subsurface/save-xml.c

636 lines
16 KiB
C
Raw Normal View History

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include "dive.h"
#include "device.h"
#include "membuffer.h"
/*
* We're outputting utf8 in xml.
* We need to quote the characters <, >, &.
*
* Technically I don't think we'd necessarily need to quote the control
* characters, but at least libxml2 doesn't like them. It doesn't even
* allow them quoted. So we just skip them and replace them with '?'.
*
* If we do this for attributes, we need to quote the quotes we use too.
*/
static void quote(struct membuffer *b, const char *text, int is_attribute)
{
const char *p = text;
for (;;) {
const char *escape;
switch (*p++) {
default:
continue;
case 0:
escape = NULL;
break;
case 1 ... 8:
case 11: case 12:
case 14 ... 31:
escape = "?";
break;
case '<':
escape = "&lt;";
break;
case '>':
escape = "&gt;";
break;
case '&':
escape = "&amp;";
break;
case '\'':
if (!is_attribute)
continue;
escape = "&apos;";
break;
case '\"':
if (!is_attribute)
continue;
escape = "&quot;";
break;
}
put_bytes(b, text, (p - text - 1));
if (!escape)
break;
put_string(b, escape);
text = p;
}
}
static void show_utf8(struct membuffer *b, const char *text, const char *pre, const char *post, int is_attribute)
{
int len;
if (!text)
return;
while (isspace(*text))
text++;
len = strlen(text);
if (!len)
return;
while (len && isspace(text[len-1]))
len--;
/* FIXME! Quoting! */
put_string(b, pre);
quote(b, text, is_attribute);
put_string(b, post);
}
static void save_depths(struct membuffer *b, struct divecomputer *dc)
{
/* What's the point of this dive entry again? */
if (!dc->maxdepth.mm && !dc->meandepth.mm)
return;
put_string(b, " <depth");
put_depth(b, dc->maxdepth, " max='", " m'");
put_depth(b, dc->meandepth, " mean='", " m'");
put_string(b, " />\n");
}
static void save_dive_temperature(struct membuffer *b, struct dive *dive)
{
if (!dive->airtemp.mkelvin && !dive->watertemp.mkelvin)
return;
if (dive->airtemp.mkelvin == dc_airtemp(&dive->dc) && dive->watertemp.mkelvin == dc_watertemp(&dive->dc))
return;
put_string(b, " <divetemperature");
if (dive->airtemp.mkelvin != dc_airtemp(&dive->dc))
put_temperature(b, dive->airtemp, " air='", " C'");
if (dive->watertemp.mkelvin != dc_watertemp(&dive->dc))
put_temperature(b, dive->watertemp, " water='", " C'");
put_string(b, "/>\n");
}
static void save_temperatures(struct membuffer *b, struct divecomputer *dc)
{
if (!dc->airtemp.mkelvin && !dc->watertemp.mkelvin)
return;
put_string(b, " <temperature");
put_temperature(b, dc->airtemp, " air='", " C'");
put_temperature(b, dc->watertemp, " water='", " C'");
put_string(b, " />\n");
}
static void save_airpressure(struct membuffer *b, struct divecomputer *dc)
{
if (!dc->surface_pressure.mbar)
return;
put_string(b, " <surface");
put_pressure(b, dc->surface_pressure, " pressure='", " bar'");
put_string(b, " />\n");
}
static void save_salinity(struct membuffer *b, struct divecomputer *dc)
{
/* only save if we have a value that isn't the default of sea water */
if (!dc->salinity || dc->salinity == SEAWATER_SALINITY)
return;
put_string(b, " <water");
put_salinity(b, dc->salinity, " salinity='", " g/l'");
put_string(b, " />\n");
}
/*
* Format degrees to within 6 decimal places. That's about 0.1m
* on a great circle (ie longitude at equator). And micro-degrees
* is also enough to fit in a fixed-point 32-bit integer.
*/
static int format_degrees(char *buffer, degrees_t value)
{
int udeg = value.udeg;
const char *sign = "";
if (udeg < 0) {
sign = "-";
udeg = -udeg;
}
return sprintf(buffer, "%s%u.%06u",
sign, udeg / 1000000, udeg % 1000000);
}
static int format_location(char *buffer, degrees_t latitude, degrees_t longitude)
{
int len = sprintf(buffer, "gps='");
len += format_degrees(buffer+len, latitude);
buffer[len++] = ' ';
len += format_degrees(buffer+len, longitude);
buffer[len++] = '\'';
return len;
}
static void show_location(struct membuffer *b, struct dive *dive)
{
char buffer[80];
const char *prefix = " <location>";
degrees_t latitude = dive->latitude;
degrees_t longitude = dive->longitude;
/*
* Ok, theoretically I guess you could dive at
* exactly 0,0. But we don't support that. So
* if you do, just fudge it a bit, and say that
* you dove a few meters away.
*/
if (latitude.udeg || longitude.udeg) {
int len = sprintf(buffer, " <location ");
len += format_location(buffer+len, latitude, longitude);
if (!dive->location) {
memcpy(buffer+len, "/>\n", 4);
put_string(b, buffer);
return;
}
buffer[len++] = '>';
buffer[len] = 0;
prefix = buffer;
}
show_utf8(b, dive->location, prefix,"</location>\n", 0);
}
static void save_overview(struct membuffer *b, struct dive *dive)
{
show_location(b, dive);
show_utf8(b, dive->divemaster, " <divemaster>","</divemaster>\n", 0);
show_utf8(b, dive->buddy, " <buddy>","</buddy>\n", 0);
show_utf8(b, dive->notes, " <notes>","</notes>\n", 0);
show_utf8(b, dive->suit, " <suit>","</suit>\n", 0);
}
Fix missing save of (almost empty) cylinder information If we have no explicit cylinder info at all (it's normal air, no size or working pressure information, and no beginning/end pressure information), we don't save the cylinders in question because that would be redundant. Such non-saved cylinders may still show up in the equipment list because there may be implicit mention of them elsewhere, notably due to sample data, so not saving them is the right thing to do - there is nothing to save. However, we missed one case: if there were other cylinders that *did* have explicit information in it following such an uninteresting cylinder, we do need to save the cylinder information for the useless case - if only in order to be able to save the non-useless information for subsequent cylinders. This patch does that. Now, if you had an air-filled cylinder with no information as your first cylinder, and a 51% nitrox as your second one, it will save that information as <cylinder /> <cylinder o2='51.0%' /> rather than dropping the cylinder information entirely. This bug has been there for a long time, and was hidden by the fact that normally you'd fill in cylinder descriptions etc after importing new dives. It also used to be that we saved the cylinder beginning/end pressure even if that was generated from the sample data, so if you imported from a air-integrated computer and had samples for that cylinder, we used to save it even though it was technically redundant. We stopped saving redundant air sample information in commit 0089dd8819b7 ("Don't save cylinder start/end pressures unless set by hand"). Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Removed start and end in save_cylinder_info(). These two variables are no longer used. Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-09-21 21:06:57 +00:00
static int nr_cylinders(struct dive *dive)
{
int nr;
for (nr = MAX_CYLINDERS; nr; --nr) {
cylinder_t *cylinder = dive->cylinder+nr-1;
if (!cylinder_nodata(cylinder))
break;
}
return nr;
}
static void save_cylinder_info(struct membuffer *b, struct dive *dive)
{
Fix missing save of (almost empty) cylinder information If we have no explicit cylinder info at all (it's normal air, no size or working pressure information, and no beginning/end pressure information), we don't save the cylinders in question because that would be redundant. Such non-saved cylinders may still show up in the equipment list because there may be implicit mention of them elsewhere, notably due to sample data, so not saving them is the right thing to do - there is nothing to save. However, we missed one case: if there were other cylinders that *did* have explicit information in it following such an uninteresting cylinder, we do need to save the cylinder information for the useless case - if only in order to be able to save the non-useless information for subsequent cylinders. This patch does that. Now, if you had an air-filled cylinder with no information as your first cylinder, and a 51% nitrox as your second one, it will save that information as <cylinder /> <cylinder o2='51.0%' /> rather than dropping the cylinder information entirely. This bug has been there for a long time, and was hidden by the fact that normally you'd fill in cylinder descriptions etc after importing new dives. It also used to be that we saved the cylinder beginning/end pressure even if that was generated from the sample data, so if you imported from a air-integrated computer and had samples for that cylinder, we used to save it even though it was technically redundant. We stopped saving redundant air sample information in commit 0089dd8819b7 ("Don't save cylinder start/end pressures unless set by hand"). Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Removed start and end in save_cylinder_info(). These two variables are no longer used. Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-09-21 21:06:57 +00:00
int i, nr;
Fix missing save of (almost empty) cylinder information If we have no explicit cylinder info at all (it's normal air, no size or working pressure information, and no beginning/end pressure information), we don't save the cylinders in question because that would be redundant. Such non-saved cylinders may still show up in the equipment list because there may be implicit mention of them elsewhere, notably due to sample data, so not saving them is the right thing to do - there is nothing to save. However, we missed one case: if there were other cylinders that *did* have explicit information in it following such an uninteresting cylinder, we do need to save the cylinder information for the useless case - if only in order to be able to save the non-useless information for subsequent cylinders. This patch does that. Now, if you had an air-filled cylinder with no information as your first cylinder, and a 51% nitrox as your second one, it will save that information as <cylinder /> <cylinder o2='51.0%' /> rather than dropping the cylinder information entirely. This bug has been there for a long time, and was hidden by the fact that normally you'd fill in cylinder descriptions etc after importing new dives. It also used to be that we saved the cylinder beginning/end pressure even if that was generated from the sample data, so if you imported from a air-integrated computer and had samples for that cylinder, we used to save it even though it was technically redundant. We stopped saving redundant air sample information in commit 0089dd8819b7 ("Don't save cylinder start/end pressures unless set by hand"). Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Removed start and end in save_cylinder_info(). These two variables are no longer used. Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-09-21 21:06:57 +00:00
nr = nr_cylinders(dive);
for (i = 0; i < nr; i++) {
cylinder_t *cylinder = dive->cylinder+i;
int volume = cylinder->type.size.mliter;
const char *description = cylinder->type.description;
int o2 = cylinder->gasmix.o2.permille;
int he = cylinder->gasmix.he.permille;
put_format(b, " <cylinder");
Don't save cylinder working pressure It was a mistake to save it - and I did it just because other dive managers did. It's a totally nonsensical measure, and nobody cares. The only thing that matters is the size of the cylinder, and the *actual* pressures. Those give actual air consumption numbers, and are meaningful and unambiguous. So the "working pressure" for a cylinder is pointless except for two things: - if you don't know the actual physical size, you need the "working pressure" along with the air size (eg "85 cuft") in order to compute the physical size. So we do use the working pressure on *input* from systems that report cylinder sizes that way. - People may well want to know what kind of cylinder they were diving, and again, you can make a good guess about this from the working pressure. So saving information like "HP100+" for the cylinder would be a good thing. But notice how in neither case do we actually want to save the working pressure itself. And in fact saving it actually makes the output format ambiguous: if we give both size and working pressure, what does 'size' mean? Is it physical size in liters, or air size in cu ft? So saving working pressure is just wrong. Get rid of it. I'm going to add some kind of "cylinder description" thing, which we can save instead (and perhaps guess standard cylinders from input like the working pressure from dive logs that don't do this sanely - which is all of them, as far as I can tell). Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2011-09-04 18:23:41 +00:00
if (volume)
put_milli(b, " size='", volume, " l'");
put_pressure(b, cylinder->type.workingpressure, " workpressure='", " bar'");
show_utf8(b, description, " description='", "'", 1);
if (o2) {
put_format(b, " o2='%u.%u%%'", FRACTION(o2, 10));
if (he)
put_format(b, " he='%u.%u%%'", FRACTION(he, 10));
}
put_pressure(b, cylinder->start, " start='", " bar'");
put_pressure(b, cylinder->end, " end='", " bar'");
put_format(b, " />\n");
}
}
static int nr_weightsystems(struct dive *dive)
{
int nr;
for (nr = MAX_WEIGHTSYSTEMS; nr; --nr) {
weightsystem_t *ws = dive->weightsystem+nr-1;
if (!weightsystem_none(ws))
break;
}
return nr;
}
static void save_weightsystem_info(struct membuffer *b, struct dive *dive)
{
int i, nr;
nr = nr_weightsystems(dive);
for (i = 0; i < nr; i++) {
weightsystem_t *ws = dive->weightsystem+i;
int grams = ws->weight.grams;
const char *description = ws->description;
put_format(b, " <weightsystem");
put_milli(b, " weight='", grams, " kg'");
show_utf8(b, description, " description='", "'", 1);
put_format(b, " />\n");
}
}
static void show_index(struct membuffer *b, int value, const char *pre, const char *post)
{
if (value)
put_format(b, " %s%d%s", pre, value, post);
}
static void save_sample(struct membuffer *b, struct sample *sample, struct sample *old)
{
put_format(b, " <sample time='%u:%02u min'", FRACTION(sample->time.seconds,60));
put_milli(b, " depth='", sample->depth.mm, " m'");
put_temperature(b, sample->temperature, " temp='", " C'");
put_pressure(b, sample->cylinderpressure, " pressure='", " bar'");
First step in cleaning up cylinder pressure sensor logic This clarifies/changes the meaning of our "cylinderindex" entry in our samples. It has been rather confused, because different dive computers have done things differently, and the naming really hasn't helped. There are two totally different - and independent - cylinder "indexes": - the pressure sensor index, which indicates which cylinder the sensor data is from. - the "active cylinder" index, which indicates which cylinder we actually breathe from. These two values really are totally independent, and have nothing what-so-ever to do with each other. The sensor index may well be fixed: many dive computers only support a single pressure sensor (whether wireless or wired), and the sensor index is thus always zero. Other dive computers may support multiple pressure sensors, and the gas switch event may - or may not - indicate that the sensor changed too. A dive computer might give the sensor data for *all* cylinders it can read, regardless of which one is the one we're actively breathing. In fact, some dive computers might give sensor data for not just *your* cylinder, but your buddies. This patch renames "cylinderindex" in the samples as "sensor", making it quite clear that it's about which sensor index the pressure data in the sample is about. The way we figure out which is the currently active gas is with an explicit has change event. If a computer (like the Uemis Zurich) joins the two concepts together, then a sensor change should also create a gas switch event. This patch also changes the Uemis importer to do that. Finally, it should be noted that the plot info works totally separately from the sample data, and is about what we actually *display*, not about the sample pressures etc. In the plot info, the "cylinderindex" does in fact mean the currently active cylinder, and while it is initially set to match the sensor information from the samples, we then walk the gas change events and fix it up - and if the active cylinder differs from the sensor cylinder, we clear the sensor data. [Dirk Hohndel: this conflicted with some of my recent changes - I think I merged things correctly...] Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-31 04:00:51 +00:00
/*
* We only show sensor information for samples with pressure, and only if it
* changed from the previous sensor we showed.
*/
if (sample->cylinderpressure.mbar && sample->sensor != old->sensor) {
put_format(b, " sensor='%d'", sample->sensor);
First step in cleaning up cylinder pressure sensor logic This clarifies/changes the meaning of our "cylinderindex" entry in our samples. It has been rather confused, because different dive computers have done things differently, and the naming really hasn't helped. There are two totally different - and independent - cylinder "indexes": - the pressure sensor index, which indicates which cylinder the sensor data is from. - the "active cylinder" index, which indicates which cylinder we actually breathe from. These two values really are totally independent, and have nothing what-so-ever to do with each other. The sensor index may well be fixed: many dive computers only support a single pressure sensor (whether wireless or wired), and the sensor index is thus always zero. Other dive computers may support multiple pressure sensors, and the gas switch event may - or may not - indicate that the sensor changed too. A dive computer might give the sensor data for *all* cylinders it can read, regardless of which one is the one we're actively breathing. In fact, some dive computers might give sensor data for not just *your* cylinder, but your buddies. This patch renames "cylinderindex" in the samples as "sensor", making it quite clear that it's about which sensor index the pressure data in the sample is about. The way we figure out which is the currently active gas is with an explicit has change event. If a computer (like the Uemis Zurich) joins the two concepts together, then a sensor change should also create a gas switch event. This patch also changes the Uemis importer to do that. Finally, it should be noted that the plot info works totally separately from the sample data, and is about what we actually *display*, not about the sample pressures etc. In the plot info, the "cylinderindex" does in fact mean the currently active cylinder, and while it is initially set to match the sensor information from the samples, we then walk the gas change events and fix it up - and if the active cylinder differs from the sensor cylinder, we clear the sensor data. [Dirk Hohndel: this conflicted with some of my recent changes - I think I merged things correctly...] Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-31 04:00:51 +00:00
old->sensor = sample->sensor;
}
/* the deco/ndl values are stored whenever they change */
First step in cleaning up cylinder pressure sensor logic This clarifies/changes the meaning of our "cylinderindex" entry in our samples. It has been rather confused, because different dive computers have done things differently, and the naming really hasn't helped. There are two totally different - and independent - cylinder "indexes": - the pressure sensor index, which indicates which cylinder the sensor data is from. - the "active cylinder" index, which indicates which cylinder we actually breathe from. These two values really are totally independent, and have nothing what-so-ever to do with each other. The sensor index may well be fixed: many dive computers only support a single pressure sensor (whether wireless or wired), and the sensor index is thus always zero. Other dive computers may support multiple pressure sensors, and the gas switch event may - or may not - indicate that the sensor changed too. A dive computer might give the sensor data for *all* cylinders it can read, regardless of which one is the one we're actively breathing. In fact, some dive computers might give sensor data for not just *your* cylinder, but your buddies. This patch renames "cylinderindex" in the samples as "sensor", making it quite clear that it's about which sensor index the pressure data in the sample is about. The way we figure out which is the currently active gas is with an explicit has change event. If a computer (like the Uemis Zurich) joins the two concepts together, then a sensor change should also create a gas switch event. This patch also changes the Uemis importer to do that. Finally, it should be noted that the plot info works totally separately from the sample data, and is about what we actually *display*, not about the sample pressures etc. In the plot info, the "cylinderindex" does in fact mean the currently active cylinder, and while it is initially set to match the sensor information from the samples, we then walk the gas change events and fix it up - and if the active cylinder differs from the sensor cylinder, we clear the sensor data. [Dirk Hohndel: this conflicted with some of my recent changes - I think I merged things correctly...] Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-31 04:00:51 +00:00
if (sample->ndl.seconds != old->ndl.seconds) {
put_format(b, " ndl='%u:%02u min'", FRACTION(sample->ndl.seconds, 60));
First step in cleaning up cylinder pressure sensor logic This clarifies/changes the meaning of our "cylinderindex" entry in our samples. It has been rather confused, because different dive computers have done things differently, and the naming really hasn't helped. There are two totally different - and independent - cylinder "indexes": - the pressure sensor index, which indicates which cylinder the sensor data is from. - the "active cylinder" index, which indicates which cylinder we actually breathe from. These two values really are totally independent, and have nothing what-so-ever to do with each other. The sensor index may well be fixed: many dive computers only support a single pressure sensor (whether wireless or wired), and the sensor index is thus always zero. Other dive computers may support multiple pressure sensors, and the gas switch event may - or may not - indicate that the sensor changed too. A dive computer might give the sensor data for *all* cylinders it can read, regardless of which one is the one we're actively breathing. In fact, some dive computers might give sensor data for not just *your* cylinder, but your buddies. This patch renames "cylinderindex" in the samples as "sensor", making it quite clear that it's about which sensor index the pressure data in the sample is about. The way we figure out which is the currently active gas is with an explicit has change event. If a computer (like the Uemis Zurich) joins the two concepts together, then a sensor change should also create a gas switch event. This patch also changes the Uemis importer to do that. Finally, it should be noted that the plot info works totally separately from the sample data, and is about what we actually *display*, not about the sample pressures etc. In the plot info, the "cylinderindex" does in fact mean the currently active cylinder, and while it is initially set to match the sensor information from the samples, we then walk the gas change events and fix it up - and if the active cylinder differs from the sensor cylinder, we clear the sensor data. [Dirk Hohndel: this conflicted with some of my recent changes - I think I merged things correctly...] Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-31 04:00:51 +00:00
old->ndl = sample->ndl;
}
if (sample->in_deco != old->in_deco) {
put_format(b, " in_deco='%d'", sample->in_deco ? 1 : 0);
First step in cleaning up cylinder pressure sensor logic This clarifies/changes the meaning of our "cylinderindex" entry in our samples. It has been rather confused, because different dive computers have done things differently, and the naming really hasn't helped. There are two totally different - and independent - cylinder "indexes": - the pressure sensor index, which indicates which cylinder the sensor data is from. - the "active cylinder" index, which indicates which cylinder we actually breathe from. These two values really are totally independent, and have nothing what-so-ever to do with each other. The sensor index may well be fixed: many dive computers only support a single pressure sensor (whether wireless or wired), and the sensor index is thus always zero. Other dive computers may support multiple pressure sensors, and the gas switch event may - or may not - indicate that the sensor changed too. A dive computer might give the sensor data for *all* cylinders it can read, regardless of which one is the one we're actively breathing. In fact, some dive computers might give sensor data for not just *your* cylinder, but your buddies. This patch renames "cylinderindex" in the samples as "sensor", making it quite clear that it's about which sensor index the pressure data in the sample is about. The way we figure out which is the currently active gas is with an explicit has change event. If a computer (like the Uemis Zurich) joins the two concepts together, then a sensor change should also create a gas switch event. This patch also changes the Uemis importer to do that. Finally, it should be noted that the plot info works totally separately from the sample data, and is about what we actually *display*, not about the sample pressures etc. In the plot info, the "cylinderindex" does in fact mean the currently active cylinder, and while it is initially set to match the sensor information from the samples, we then walk the gas change events and fix it up - and if the active cylinder differs from the sensor cylinder, we clear the sensor data. [Dirk Hohndel: this conflicted with some of my recent changes - I think I merged things correctly...] Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-31 04:00:51 +00:00
old->in_deco = sample->in_deco;
}
if (sample->stoptime.seconds != old->stoptime.seconds) {
put_format(b, " stoptime='%u:%02u min'", FRACTION(sample->stoptime.seconds, 60));
First step in cleaning up cylinder pressure sensor logic This clarifies/changes the meaning of our "cylinderindex" entry in our samples. It has been rather confused, because different dive computers have done things differently, and the naming really hasn't helped. There are two totally different - and independent - cylinder "indexes": - the pressure sensor index, which indicates which cylinder the sensor data is from. - the "active cylinder" index, which indicates which cylinder we actually breathe from. These two values really are totally independent, and have nothing what-so-ever to do with each other. The sensor index may well be fixed: many dive computers only support a single pressure sensor (whether wireless or wired), and the sensor index is thus always zero. Other dive computers may support multiple pressure sensors, and the gas switch event may - or may not - indicate that the sensor changed too. A dive computer might give the sensor data for *all* cylinders it can read, regardless of which one is the one we're actively breathing. In fact, some dive computers might give sensor data for not just *your* cylinder, but your buddies. This patch renames "cylinderindex" in the samples as "sensor", making it quite clear that it's about which sensor index the pressure data in the sample is about. The way we figure out which is the currently active gas is with an explicit has change event. If a computer (like the Uemis Zurich) joins the two concepts together, then a sensor change should also create a gas switch event. This patch also changes the Uemis importer to do that. Finally, it should be noted that the plot info works totally separately from the sample data, and is about what we actually *display*, not about the sample pressures etc. In the plot info, the "cylinderindex" does in fact mean the currently active cylinder, and while it is initially set to match the sensor information from the samples, we then walk the gas change events and fix it up - and if the active cylinder differs from the sensor cylinder, we clear the sensor data. [Dirk Hohndel: this conflicted with some of my recent changes - I think I merged things correctly...] Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-31 04:00:51 +00:00
old->stoptime = sample->stoptime;
}
if (sample->stopdepth.mm != old->stopdepth.mm) {
put_milli(b, " stopdepth='", sample->stopdepth.mm, " m'");
First step in cleaning up cylinder pressure sensor logic This clarifies/changes the meaning of our "cylinderindex" entry in our samples. It has been rather confused, because different dive computers have done things differently, and the naming really hasn't helped. There are two totally different - and independent - cylinder "indexes": - the pressure sensor index, which indicates which cylinder the sensor data is from. - the "active cylinder" index, which indicates which cylinder we actually breathe from. These two values really are totally independent, and have nothing what-so-ever to do with each other. The sensor index may well be fixed: many dive computers only support a single pressure sensor (whether wireless or wired), and the sensor index is thus always zero. Other dive computers may support multiple pressure sensors, and the gas switch event may - or may not - indicate that the sensor changed too. A dive computer might give the sensor data for *all* cylinders it can read, regardless of which one is the one we're actively breathing. In fact, some dive computers might give sensor data for not just *your* cylinder, but your buddies. This patch renames "cylinderindex" in the samples as "sensor", making it quite clear that it's about which sensor index the pressure data in the sample is about. The way we figure out which is the currently active gas is with an explicit has change event. If a computer (like the Uemis Zurich) joins the two concepts together, then a sensor change should also create a gas switch event. This patch also changes the Uemis importer to do that. Finally, it should be noted that the plot info works totally separately from the sample data, and is about what we actually *display*, not about the sample pressures etc. In the plot info, the "cylinderindex" does in fact mean the currently active cylinder, and while it is initially set to match the sensor information from the samples, we then walk the gas change events and fix it up - and if the active cylinder differs from the sensor cylinder, we clear the sensor data. [Dirk Hohndel: this conflicted with some of my recent changes - I think I merged things correctly...] Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-31 04:00:51 +00:00
old->stopdepth = sample->stopdepth;
}
if (sample->cns != old->cns) {
put_format(b, " cns='%u%%'", sample->cns);
First step in cleaning up cylinder pressure sensor logic This clarifies/changes the meaning of our "cylinderindex" entry in our samples. It has been rather confused, because different dive computers have done things differently, and the naming really hasn't helped. There are two totally different - and independent - cylinder "indexes": - the pressure sensor index, which indicates which cylinder the sensor data is from. - the "active cylinder" index, which indicates which cylinder we actually breathe from. These two values really are totally independent, and have nothing what-so-ever to do with each other. The sensor index may well be fixed: many dive computers only support a single pressure sensor (whether wireless or wired), and the sensor index is thus always zero. Other dive computers may support multiple pressure sensors, and the gas switch event may - or may not - indicate that the sensor changed too. A dive computer might give the sensor data for *all* cylinders it can read, regardless of which one is the one we're actively breathing. In fact, some dive computers might give sensor data for not just *your* cylinder, but your buddies. This patch renames "cylinderindex" in the samples as "sensor", making it quite clear that it's about which sensor index the pressure data in the sample is about. The way we figure out which is the currently active gas is with an explicit has change event. If a computer (like the Uemis Zurich) joins the two concepts together, then a sensor change should also create a gas switch event. This patch also changes the Uemis importer to do that. Finally, it should be noted that the plot info works totally separately from the sample data, and is about what we actually *display*, not about the sample pressures etc. In the plot info, the "cylinderindex" does in fact mean the currently active cylinder, and while it is initially set to match the sensor information from the samples, we then walk the gas change events and fix it up - and if the active cylinder differs from the sensor cylinder, we clear the sensor data. [Dirk Hohndel: this conflicted with some of my recent changes - I think I merged things correctly...] Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-31 04:00:51 +00:00
old->cns = sample->cns;
}
if (sample->po2 != old->po2) {
put_milli(b, " po2='", sample->po2, " bar'");
First step in cleaning up cylinder pressure sensor logic This clarifies/changes the meaning of our "cylinderindex" entry in our samples. It has been rather confused, because different dive computers have done things differently, and the naming really hasn't helped. There are two totally different - and independent - cylinder "indexes": - the pressure sensor index, which indicates which cylinder the sensor data is from. - the "active cylinder" index, which indicates which cylinder we actually breathe from. These two values really are totally independent, and have nothing what-so-ever to do with each other. The sensor index may well be fixed: many dive computers only support a single pressure sensor (whether wireless or wired), and the sensor index is thus always zero. Other dive computers may support multiple pressure sensors, and the gas switch event may - or may not - indicate that the sensor changed too. A dive computer might give the sensor data for *all* cylinders it can read, regardless of which one is the one we're actively breathing. In fact, some dive computers might give sensor data for not just *your* cylinder, but your buddies. This patch renames "cylinderindex" in the samples as "sensor", making it quite clear that it's about which sensor index the pressure data in the sample is about. The way we figure out which is the currently active gas is with an explicit has change event. If a computer (like the Uemis Zurich) joins the two concepts together, then a sensor change should also create a gas switch event. This patch also changes the Uemis importer to do that. Finally, it should be noted that the plot info works totally separately from the sample data, and is about what we actually *display*, not about the sample pressures etc. In the plot info, the "cylinderindex" does in fact mean the currently active cylinder, and while it is initially set to match the sensor information from the samples, we then walk the gas change events and fix it up - and if the active cylinder differs from the sensor cylinder, we clear the sensor data. [Dirk Hohndel: this conflicted with some of my recent changes - I think I merged things correctly...] Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-31 04:00:51 +00:00
old->po2 = sample->po2;
}
put_format(b, " />\n");
}
static void save_one_event(struct membuffer *b, struct event *ev)
{
put_format(b, " <event time='%d:%02d min'", FRACTION(ev->time.seconds,60));
show_index(b, ev->type, "type='", "'");
show_index(b, ev->flags, "flags='", "'");
show_index(b, ev->value, "value='", "'");
show_utf8(b, ev->name, " name='", "'", 1);
put_format(b, " />\n");
}
static void save_events(struct membuffer *b, struct event *ev)
{
while (ev) {
save_one_event(b, ev);
ev = ev->next;
}
}
static void save_tags(struct membuffer *b, struct tag_entry *tag_list)
{
int more = 0;
struct tag_entry *tmp = tag_list->next;
/* Only write tag attribute if the list contains at least one item */
if (tmp != NULL) {
put_format(b, " tags='");
while (tmp != NULL) {
if (more)
put_format(b, ", ");
/* If the tag has been translated, write the source to the xml file */
if (tmp->tag->source != NULL)
quote(b, tmp->tag->source, 0);
else
quote(b, tmp->tag->name, 0);
tmp = tmp->next;
more = 1;
}
put_format(b, "'");
}
}
static void show_date(struct membuffer *b, timestamp_t when)
First cut of explicit trip tracking This code establishes the explicit trip data structures and loads and saves them in the XML data. No attempts are made to edit / modify the trips, yet. Loading XML files without trip data creates the trips based on timing as before. Saving out the same, unmodified data will create 'trip' entries in the XML file with a 'number' that reflects the number of dives in that trip. The trip tag also stores the beginning time of the first dive in the trip and the location of the trip (which we display in the summary entries in the UI). The logic allows for dives that aren't part of a dive trip. All other dives simply belong to the "previous" dive trip - i.e. the dive trip with the latest start time that is earlier or equal to the start time of this dive. This logic significantly simplifies the tracking of trips compared to other approaches that I have tried. The automatic grouping into trips now is an option that defaults to off (as it makes changes to the XML file - and people who don't want this feature shouldn't have trips added to their XML files that they then need to manually remove). For now you have to select this option, then exit the program and start it again. Still to do is to trigger the trip generation at run time. We also need a way to mark dives as not part of trips and to allow options to combine trips, split trips, edit trip location data, etc. The code has only had some limited testing when opening multiple files. The code is known to fail if a location name contains unquoted special characters like an "'". This commit also fixes a visual inconsistency in the preferences dialog where the font selector button didn't have a frame around it that told you what this option was about. Inspired-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-08-22 05:04:24 +00:00
{
struct tm tm;
utc_mkdate(when, &tm);
First cut of explicit trip tracking This code establishes the explicit trip data structures and loads and saves them in the XML data. No attempts are made to edit / modify the trips, yet. Loading XML files without trip data creates the trips based on timing as before. Saving out the same, unmodified data will create 'trip' entries in the XML file with a 'number' that reflects the number of dives in that trip. The trip tag also stores the beginning time of the first dive in the trip and the location of the trip (which we display in the summary entries in the UI). The logic allows for dives that aren't part of a dive trip. All other dives simply belong to the "previous" dive trip - i.e. the dive trip with the latest start time that is earlier or equal to the start time of this dive. This logic significantly simplifies the tracking of trips compared to other approaches that I have tried. The automatic grouping into trips now is an option that defaults to off (as it makes changes to the XML file - and people who don't want this feature shouldn't have trips added to their XML files that they then need to manually remove). For now you have to select this option, then exit the program and start it again. Still to do is to trigger the trip generation at run time. We also need a way to mark dives as not part of trips and to allow options to combine trips, split trips, edit trip location data, etc. The code has only had some limited testing when opening multiple files. The code is known to fail if a location name contains unquoted special characters like an "'". This commit also fixes a visual inconsistency in the preferences dialog where the font selector button didn't have a frame around it that told you what this option was about. Inspired-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-08-22 05:04:24 +00:00
put_format(b, " date='%04u-%02u-%02u'",
tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday);
put_format(b, " time='%02u:%02u:%02u'",
tm.tm_hour, tm.tm_min, tm.tm_sec);
}
static void save_samples(struct membuffer *b, int nr, struct sample *s)
{
First step in cleaning up cylinder pressure sensor logic This clarifies/changes the meaning of our "cylinderindex" entry in our samples. It has been rather confused, because different dive computers have done things differently, and the naming really hasn't helped. There are two totally different - and independent - cylinder "indexes": - the pressure sensor index, which indicates which cylinder the sensor data is from. - the "active cylinder" index, which indicates which cylinder we actually breathe from. These two values really are totally independent, and have nothing what-so-ever to do with each other. The sensor index may well be fixed: many dive computers only support a single pressure sensor (whether wireless or wired), and the sensor index is thus always zero. Other dive computers may support multiple pressure sensors, and the gas switch event may - or may not - indicate that the sensor changed too. A dive computer might give the sensor data for *all* cylinders it can read, regardless of which one is the one we're actively breathing. In fact, some dive computers might give sensor data for not just *your* cylinder, but your buddies. This patch renames "cylinderindex" in the samples as "sensor", making it quite clear that it's about which sensor index the pressure data in the sample is about. The way we figure out which is the currently active gas is with an explicit has change event. If a computer (like the Uemis Zurich) joins the two concepts together, then a sensor change should also create a gas switch event. This patch also changes the Uemis importer to do that. Finally, it should be noted that the plot info works totally separately from the sample data, and is about what we actually *display*, not about the sample pressures etc. In the plot info, the "cylinderindex" does in fact mean the currently active cylinder, and while it is initially set to match the sensor information from the samples, we then walk the gas change events and fix it up - and if the active cylinder differs from the sensor cylinder, we clear the sensor data. [Dirk Hohndel: this conflicted with some of my recent changes - I think I merged things correctly...] Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-31 04:00:51 +00:00
struct sample dummy = { };
while (--nr >= 0) {
save_sample(b, s, &dummy);
s++;
}
}
static void save_dc(struct membuffer *b, struct dive *dive, struct divecomputer *dc)
{
put_format(b, " <divecomputer");
show_utf8(b, dc->model, " model='", "'", 1);
if (dc->deviceid)
put_format(b, " deviceid='%08x'", dc->deviceid);
if (dc->diveid)
put_format(b, " diveid='%08x'", dc->diveid);
if (dc->when && dc->when != dive->when)
show_date(b, dc->when);
if (dc->duration.seconds && dc->duration.seconds != dive->dc.duration.seconds)
put_duration(b, dc->duration, " duration='", " min'");
put_format(b, ">\n");
save_depths(b, dc);
save_temperatures(b, dc);
save_airpressure(b, dc);
save_salinity(b, dc);
put_duration(b, dc->surfacetime, " <surfacetime>", " min</surfacetime>\n");
save_events(b, dc->events);
save_samples(b, dc->samples, dc->sample);
put_format(b, " </divecomputer>\n");
}
void save_one_dive(struct membuffer *b, struct dive *dive)
{
struct divecomputer *dc;
put_string(b, "<dive");
if (dive->number)
put_format(b, " number='%d'", dive->number);
if (dive->tripflag == NO_TRIP)
put_format(b, " tripflag='NOTRIP'");
if (dive->rating)
put_format(b, " rating='%d'", dive->rating);
if (dive->visibility)
put_format(b, " visibility='%d'", dive->visibility);
if (dive->tag_list != NULL)
save_tags(b, dive->tag_list);
show_date(b, dive->when);
put_format(b, " duration='%u:%02u min'>\n",
FRACTION(dive->dc.duration.seconds, 60));
save_overview(b, dive);
save_cylinder_info(b, dive);
save_weightsystem_info(b, dive);
save_dive_temperature(b, dive);
/* Save the dive computer data */
dc = &dive->dc;
do {
save_dc(b, dive, dc);
dc = dc->next;
} while (dc);
put_format(b, "</dive>\n");
}
void save_dive(FILE *f, struct dive *dive)
{
struct membuffer buf = {0};
save_one_dive(&buf, dive);
flush_buffer(&buf, f);
}
static void save_trip(struct membuffer *b, dive_trip_t *trip)
Allow overlapping (and disjoint) dive trips We used to have the rule that a dive trip has to have all dives in it in sequential order, even though our XML file really is much more flexible, and allows arbitrary nesting of dives within a dive trip. Put another way, the old model had fairly inflexible rules: - the dive array is sorted by time - a dive trip is always a contiguous slice of this sorted array which makes perfect sense when you think of the dive and trip list as a physical activity by one person, but leads to various very subtle issues in the general case when there are no guarantees that the user then uses subsurface that way. In particular, if you load the XML files of two divers that have overlapping dive trips, the end result is incredibly messy, and does not conform to the above model at all. There's two ways to enforce such conformance: - disallow that kind of behavior entirely. This is actually hard. Our XML files aren't date-based, they are based on XML nesting rules, and even a single XML file can have nesting that violates the date ordering. With multiple XML files, it's trivial to do in practice, and while we could just fail at loading, the failure would have to be a hard failure that leaves the user no way to use the data at all. - try to "fix it up" by sorting, splitting, and combining dive trips automatically. Dirk had a patch to do this, but it really does destroy the actual dive data: if you load both mine and Dirk's dive trips, you ended up with a result that followed the above two technical rules, but that didn't actually make any *sense*. So this patch doesn't try to enforce the rules, and instead just changes them to be more generic: - the dive array is still sorted by dive time - a dive trip is just an arbitrary collection of dives. The relaxed rules means that mixing dives and dive trips for two people is trivial, and we can easily handle any XML file. The dive trip is defined by the XML nesting level, and is totally independent of any date-based sorting. It does require a few things: - when we save our dive data, we have to do it hierarchically by dive trip, not just by walking the dive array linearly. - similarly, when we create the dive tree model, we can't just blindly walk the array of dives one by one, we have to look up the correct trip (parent) - when we try to merge two dives that are adjacent (by date sorting), we can't do it if they are in different trips. but apart from that, nothing else really changes. NOTE! Despite the new relaxed model, creating totally disjoing dive trips is not all that easy (nor is there any *reason* for it to be easty). Our GUI interfaces still are "add dive to trip above" etc, and the automatic adding of dives to dive trips is obviously still based on date. So this does not really change the expected normal usage, the relaxed data structure rules just mean that we don't need to worry about the odd cases as much, because we can just let them be. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-30 19:00:37 +00:00
{
int i;
struct dive *dive;
put_format(b, "<trip");
show_date(b, trip->when);
show_utf8(b, trip->location, " location=\'","\'", 1);
put_format(b, ">\n");
show_utf8(b, trip->notes, "<notes>","</notes>\n", 0);
Allow overlapping (and disjoint) dive trips We used to have the rule that a dive trip has to have all dives in it in sequential order, even though our XML file really is much more flexible, and allows arbitrary nesting of dives within a dive trip. Put another way, the old model had fairly inflexible rules: - the dive array is sorted by time - a dive trip is always a contiguous slice of this sorted array which makes perfect sense when you think of the dive and trip list as a physical activity by one person, but leads to various very subtle issues in the general case when there are no guarantees that the user then uses subsurface that way. In particular, if you load the XML files of two divers that have overlapping dive trips, the end result is incredibly messy, and does not conform to the above model at all. There's two ways to enforce such conformance: - disallow that kind of behavior entirely. This is actually hard. Our XML files aren't date-based, they are based on XML nesting rules, and even a single XML file can have nesting that violates the date ordering. With multiple XML files, it's trivial to do in practice, and while we could just fail at loading, the failure would have to be a hard failure that leaves the user no way to use the data at all. - try to "fix it up" by sorting, splitting, and combining dive trips automatically. Dirk had a patch to do this, but it really does destroy the actual dive data: if you load both mine and Dirk's dive trips, you ended up with a result that followed the above two technical rules, but that didn't actually make any *sense*. So this patch doesn't try to enforce the rules, and instead just changes them to be more generic: - the dive array is still sorted by dive time - a dive trip is just an arbitrary collection of dives. The relaxed rules means that mixing dives and dive trips for two people is trivial, and we can easily handle any XML file. The dive trip is defined by the XML nesting level, and is totally independent of any date-based sorting. It does require a few things: - when we save our dive data, we have to do it hierarchically by dive trip, not just by walking the dive array linearly. - similarly, when we create the dive tree model, we can't just blindly walk the array of dives one by one, we have to look up the correct trip (parent) - when we try to merge two dives that are adjacent (by date sorting), we can't do it if they are in different trips. but apart from that, nothing else really changes. NOTE! Despite the new relaxed model, creating totally disjoing dive trips is not all that easy (nor is there any *reason* for it to be easty). Our GUI interfaces still are "add dive to trip above" etc, and the automatic adding of dives to dive trips is obviously still based on date. So this does not really change the expected normal usage, the relaxed data structure rules just mean that we don't need to worry about the odd cases as much, because we can just let them be. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-30 19:00:37 +00:00
/*
* Incredibly cheesy: we want to save the dives sorted, and they
* are sorted in the dive array.. So instead of using the dive
* list in the trip, we just traverse the global dive array and
* check the divetrip pointer..
*/
for_each_dive(i, dive) {
if (dive->divetrip == trip)
save_one_dive(b, dive);
Allow overlapping (and disjoint) dive trips We used to have the rule that a dive trip has to have all dives in it in sequential order, even though our XML file really is much more flexible, and allows arbitrary nesting of dives within a dive trip. Put another way, the old model had fairly inflexible rules: - the dive array is sorted by time - a dive trip is always a contiguous slice of this sorted array which makes perfect sense when you think of the dive and trip list as a physical activity by one person, but leads to various very subtle issues in the general case when there are no guarantees that the user then uses subsurface that way. In particular, if you load the XML files of two divers that have overlapping dive trips, the end result is incredibly messy, and does not conform to the above model at all. There's two ways to enforce such conformance: - disallow that kind of behavior entirely. This is actually hard. Our XML files aren't date-based, they are based on XML nesting rules, and even a single XML file can have nesting that violates the date ordering. With multiple XML files, it's trivial to do in practice, and while we could just fail at loading, the failure would have to be a hard failure that leaves the user no way to use the data at all. - try to "fix it up" by sorting, splitting, and combining dive trips automatically. Dirk had a patch to do this, but it really does destroy the actual dive data: if you load both mine and Dirk's dive trips, you ended up with a result that followed the above two technical rules, but that didn't actually make any *sense*. So this patch doesn't try to enforce the rules, and instead just changes them to be more generic: - the dive array is still sorted by dive time - a dive trip is just an arbitrary collection of dives. The relaxed rules means that mixing dives and dive trips for two people is trivial, and we can easily handle any XML file. The dive trip is defined by the XML nesting level, and is totally independent of any date-based sorting. It does require a few things: - when we save our dive data, we have to do it hierarchically by dive trip, not just by walking the dive array linearly. - similarly, when we create the dive tree model, we can't just blindly walk the array of dives one by one, we have to look up the correct trip (parent) - when we try to merge two dives that are adjacent (by date sorting), we can't do it if they are in different trips. but apart from that, nothing else really changes. NOTE! Despite the new relaxed model, creating totally disjoing dive trips is not all that easy (nor is there any *reason* for it to be easty). Our GUI interfaces still are "add dive to trip above" etc, and the automatic adding of dives to dive trips is obviously still based on date. So this does not really change the expected normal usage, the relaxed data structure rules just mean that we don't need to worry about the odd cases as much, because we can just let them be. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-30 19:00:37 +00:00
}
put_format(b, "</trip>\n");
Allow overlapping (and disjoint) dive trips We used to have the rule that a dive trip has to have all dives in it in sequential order, even though our XML file really is much more flexible, and allows arbitrary nesting of dives within a dive trip. Put another way, the old model had fairly inflexible rules: - the dive array is sorted by time - a dive trip is always a contiguous slice of this sorted array which makes perfect sense when you think of the dive and trip list as a physical activity by one person, but leads to various very subtle issues in the general case when there are no guarantees that the user then uses subsurface that way. In particular, if you load the XML files of two divers that have overlapping dive trips, the end result is incredibly messy, and does not conform to the above model at all. There's two ways to enforce such conformance: - disallow that kind of behavior entirely. This is actually hard. Our XML files aren't date-based, they are based on XML nesting rules, and even a single XML file can have nesting that violates the date ordering. With multiple XML files, it's trivial to do in practice, and while we could just fail at loading, the failure would have to be a hard failure that leaves the user no way to use the data at all. - try to "fix it up" by sorting, splitting, and combining dive trips automatically. Dirk had a patch to do this, but it really does destroy the actual dive data: if you load both mine and Dirk's dive trips, you ended up with a result that followed the above two technical rules, but that didn't actually make any *sense*. So this patch doesn't try to enforce the rules, and instead just changes them to be more generic: - the dive array is still sorted by dive time - a dive trip is just an arbitrary collection of dives. The relaxed rules means that mixing dives and dive trips for two people is trivial, and we can easily handle any XML file. The dive trip is defined by the XML nesting level, and is totally independent of any date-based sorting. It does require a few things: - when we save our dive data, we have to do it hierarchically by dive trip, not just by walking the dive array linearly. - similarly, when we create the dive tree model, we can't just blindly walk the array of dives one by one, we have to look up the correct trip (parent) - when we try to merge two dives that are adjacent (by date sorting), we can't do it if they are in different trips. but apart from that, nothing else really changes. NOTE! Despite the new relaxed model, creating totally disjoing dive trips is not all that easy (nor is there any *reason* for it to be easty). Our GUI interfaces still are "add dive to trip above" etc, and the automatic adding of dives to dive trips is obviously still based on date. So this does not really change the expected normal usage, the relaxed data structure rules just mean that we don't need to worry about the odd cases as much, because we can just let them be. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-30 19:00:37 +00:00
}
static void save_one_device(void *_f, const char * model, uint32_t deviceid,
const char *nickname, const char *serial_nr, const char *firmware)
{
struct membuffer *b = _f;
Assemble the actual Suunto serial number It turns out that the serial number returned by libdivecomputer isn't really the serial number as interpreted by the vendor. Those tend to be strings, but libdivecomputer gives us a 32bit number. Some experimenting showed that for the Suunto devies tested the serial number is encoded in that 32bit number: It so happens that the Suunto serial number strings are strings that have all numbers, but they aren't *one* number. They are four bytes representing two numbers each, and the "23500027" string is actually the four bytes 23 50 00 27 (0x17 0x32 0x00 0x1b). And libdivecomputer has incorrectly parsed those four bytes as one number, not as the encoded serial number string it is. So the value 389152795 is actually hex 0x1732001b, which is 0x17 0x32 0x00 0x1b, which is - 23 50 00 27. This should be done by libdivecomputer, but hey, in the meantime this at least shows the concept. And helps test the XML save/restore code. It depends on the two patches that create the whole "device.c" infrastructure, of course. With this, my dive file ends up having the settings section look like this: <divecomputerid model='Suunto Vyper Air' deviceid='d4629110' serial='01201094' firmware='1.1.22'/> <divecomputerid model='Suunto HelO2' deviceid='995dd566' serial='23500027' firmware='1.0.4'/> where the format of the firmware version is something I guessed at, but it was the obvious choice (again, it's byte-based, I'm ignoring the high byte that is zero for both of my Suuntos). Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2013-01-10 00:14:21 +00:00
/* Nicknames that are empty or the same as the device model are not interesting */
if (nickname) {
if (!*nickname || !strcmp(model, nickname))
Assemble the actual Suunto serial number It turns out that the serial number returned by libdivecomputer isn't really the serial number as interpreted by the vendor. Those tend to be strings, but libdivecomputer gives us a 32bit number. Some experimenting showed that for the Suunto devies tested the serial number is encoded in that 32bit number: It so happens that the Suunto serial number strings are strings that have all numbers, but they aren't *one* number. They are four bytes representing two numbers each, and the "23500027" string is actually the four bytes 23 50 00 27 (0x17 0x32 0x00 0x1b). And libdivecomputer has incorrectly parsed those four bytes as one number, not as the encoded serial number string it is. So the value 389152795 is actually hex 0x1732001b, which is 0x17 0x32 0x00 0x1b, which is - 23 50 00 27. This should be done by libdivecomputer, but hey, in the meantime this at least shows the concept. And helps test the XML save/restore code. It depends on the two patches that create the whole "device.c" infrastructure, of course. With this, my dive file ends up having the settings section look like this: <divecomputerid model='Suunto Vyper Air' deviceid='d4629110' serial='01201094' firmware='1.1.22'/> <divecomputerid model='Suunto HelO2' deviceid='995dd566' serial='23500027' firmware='1.0.4'/> where the format of the firmware version is something I guessed at, but it was the obvious choice (again, it's byte-based, I'm ignoring the high byte that is zero for both of my Suuntos). Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2013-01-10 00:14:21 +00:00
nickname = NULL;
}
/* Serial numbers that are empty are not interesting */
if (serial_nr && !*serial_nr)
serial_nr = NULL;
/* Firmware strings that are empty are not interesting */
if (firmware && !*firmware)
firmware = NULL;
Assemble the actual Suunto serial number It turns out that the serial number returned by libdivecomputer isn't really the serial number as interpreted by the vendor. Those tend to be strings, but libdivecomputer gives us a 32bit number. Some experimenting showed that for the Suunto devies tested the serial number is encoded in that 32bit number: It so happens that the Suunto serial number strings are strings that have all numbers, but they aren't *one* number. They are four bytes representing two numbers each, and the "23500027" string is actually the four bytes 23 50 00 27 (0x17 0x32 0x00 0x1b). And libdivecomputer has incorrectly parsed those four bytes as one number, not as the encoded serial number string it is. So the value 389152795 is actually hex 0x1732001b, which is 0x17 0x32 0x00 0x1b, which is - 23 50 00 27. This should be done by libdivecomputer, but hey, in the meantime this at least shows the concept. And helps test the XML save/restore code. It depends on the two patches that create the whole "device.c" infrastructure, of course. With this, my dive file ends up having the settings section look like this: <divecomputerid model='Suunto Vyper Air' deviceid='d4629110' serial='01201094' firmware='1.1.22'/> <divecomputerid model='Suunto HelO2' deviceid='995dd566' serial='23500027' firmware='1.0.4'/> where the format of the firmware version is something I guessed at, but it was the obvious choice (again, it's byte-based, I'm ignoring the high byte that is zero for both of my Suuntos). Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2013-01-10 00:14:21 +00:00
/* Do we have anything interesting about this dive computer to save? */
if (!serial_nr && !nickname && !firmware)
return;
put_format(b, "<divecomputerid");
show_utf8(b, model, " model='", "'", 1);
put_format(b, " deviceid='%08x'", deviceid);
show_utf8(b, serial_nr, " serial='", "'", 1);
show_utf8(b, firmware, " firmware='", "'", 1);
show_utf8(b, nickname, " nickname='", "'", 1);
put_format(b, "/>\n");
}
#define VERSION 2
void save_dives(const char *filename)
{
save_dives_logic(filename, false);
}
void save_dives_buffer(struct membuffer *b, const bool select_only)
{
int i;
struct dive *dive;
Allow overlapping (and disjoint) dive trips We used to have the rule that a dive trip has to have all dives in it in sequential order, even though our XML file really is much more flexible, and allows arbitrary nesting of dives within a dive trip. Put another way, the old model had fairly inflexible rules: - the dive array is sorted by time - a dive trip is always a contiguous slice of this sorted array which makes perfect sense when you think of the dive and trip list as a physical activity by one person, but leads to various very subtle issues in the general case when there are no guarantees that the user then uses subsurface that way. In particular, if you load the XML files of two divers that have overlapping dive trips, the end result is incredibly messy, and does not conform to the above model at all. There's two ways to enforce such conformance: - disallow that kind of behavior entirely. This is actually hard. Our XML files aren't date-based, they are based on XML nesting rules, and even a single XML file can have nesting that violates the date ordering. With multiple XML files, it's trivial to do in practice, and while we could just fail at loading, the failure would have to be a hard failure that leaves the user no way to use the data at all. - try to "fix it up" by sorting, splitting, and combining dive trips automatically. Dirk had a patch to do this, but it really does destroy the actual dive data: if you load both mine and Dirk's dive trips, you ended up with a result that followed the above two technical rules, but that didn't actually make any *sense*. So this patch doesn't try to enforce the rules, and instead just changes them to be more generic: - the dive array is still sorted by dive time - a dive trip is just an arbitrary collection of dives. The relaxed rules means that mixing dives and dive trips for two people is trivial, and we can easily handle any XML file. The dive trip is defined by the XML nesting level, and is totally independent of any date-based sorting. It does require a few things: - when we save our dive data, we have to do it hierarchically by dive trip, not just by walking the dive array linearly. - similarly, when we create the dive tree model, we can't just blindly walk the array of dives one by one, we have to look up the correct trip (parent) - when we try to merge two dives that are adjacent (by date sorting), we can't do it if they are in different trips. but apart from that, nothing else really changes. NOTE! Despite the new relaxed model, creating totally disjoing dive trips is not all that easy (nor is there any *reason* for it to be easty). Our GUI interfaces still are "add dive to trip above" etc, and the automatic adding of dives to dive trips is obviously still based on date. So this does not really change the expected normal usage, the relaxed data structure rules just mean that we don't need to worry about the odd cases as much, because we can just let them be. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-30 19:00:37 +00:00
dive_trip_t *trip;
First cut of explicit trip tracking This code establishes the explicit trip data structures and loads and saves them in the XML data. No attempts are made to edit / modify the trips, yet. Loading XML files without trip data creates the trips based on timing as before. Saving out the same, unmodified data will create 'trip' entries in the XML file with a 'number' that reflects the number of dives in that trip. The trip tag also stores the beginning time of the first dive in the trip and the location of the trip (which we display in the summary entries in the UI). The logic allows for dives that aren't part of a dive trip. All other dives simply belong to the "previous" dive trip - i.e. the dive trip with the latest start time that is earlier or equal to the start time of this dive. This logic significantly simplifies the tracking of trips compared to other approaches that I have tried. The automatic grouping into trips now is an option that defaults to off (as it makes changes to the XML file - and people who don't want this feature shouldn't have trips added to their XML files that they then need to manually remove). For now you have to select this option, then exit the program and start it again. Still to do is to trigger the trip generation at run time. We also need a way to mark dives as not part of trips and to allow options to combine trips, split trips, edit trip location data, etc. The code has only had some limited testing when opening multiple files. The code is known to fail if a location name contains unquoted special characters like an "'". This commit also fixes a visual inconsistency in the preferences dialog where the font selector button didn't have a frame around it that told you what this option was about. Inspired-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-08-22 05:04:24 +00:00
put_format(b, "<divelog program='subsurface' version='%d'>\n<settings>\n", VERSION);
/* save the dive computer nicknames, if any */
call_for_each_dc(b, save_one_device);
if (autogroup)
put_format(b, "<autogroup state='1' />\n");
put_format(b, "</settings>\n<dives>\n");
Allow overlapping (and disjoint) dive trips We used to have the rule that a dive trip has to have all dives in it in sequential order, even though our XML file really is much more flexible, and allows arbitrary nesting of dives within a dive trip. Put another way, the old model had fairly inflexible rules: - the dive array is sorted by time - a dive trip is always a contiguous slice of this sorted array which makes perfect sense when you think of the dive and trip list as a physical activity by one person, but leads to various very subtle issues in the general case when there are no guarantees that the user then uses subsurface that way. In particular, if you load the XML files of two divers that have overlapping dive trips, the end result is incredibly messy, and does not conform to the above model at all. There's two ways to enforce such conformance: - disallow that kind of behavior entirely. This is actually hard. Our XML files aren't date-based, they are based on XML nesting rules, and even a single XML file can have nesting that violates the date ordering. With multiple XML files, it's trivial to do in practice, and while we could just fail at loading, the failure would have to be a hard failure that leaves the user no way to use the data at all. - try to "fix it up" by sorting, splitting, and combining dive trips automatically. Dirk had a patch to do this, but it really does destroy the actual dive data: if you load both mine and Dirk's dive trips, you ended up with a result that followed the above two technical rules, but that didn't actually make any *sense*. So this patch doesn't try to enforce the rules, and instead just changes them to be more generic: - the dive array is still sorted by dive time - a dive trip is just an arbitrary collection of dives. The relaxed rules means that mixing dives and dive trips for two people is trivial, and we can easily handle any XML file. The dive trip is defined by the XML nesting level, and is totally independent of any date-based sorting. It does require a few things: - when we save our dive data, we have to do it hierarchically by dive trip, not just by walking the dive array linearly. - similarly, when we create the dive tree model, we can't just blindly walk the array of dives one by one, we have to look up the correct trip (parent) - when we try to merge two dives that are adjacent (by date sorting), we can't do it if they are in different trips. but apart from that, nothing else really changes. NOTE! Despite the new relaxed model, creating totally disjoing dive trips is not all that easy (nor is there any *reason* for it to be easty). Our GUI interfaces still are "add dive to trip above" etc, and the automatic adding of dives to dive trips is obviously still based on date. So this does not really change the expected normal usage, the relaxed data structure rules just mean that we don't need to worry about the odd cases as much, because we can just let them be. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-30 19:00:37 +00:00
for (trip = dive_trip_list; trip != NULL; trip = trip->next)
trip->index = 0;
First cut of explicit trip tracking This code establishes the explicit trip data structures and loads and saves them in the XML data. No attempts are made to edit / modify the trips, yet. Loading XML files without trip data creates the trips based on timing as before. Saving out the same, unmodified data will create 'trip' entries in the XML file with a 'number' that reflects the number of dives in that trip. The trip tag also stores the beginning time of the first dive in the trip and the location of the trip (which we display in the summary entries in the UI). The logic allows for dives that aren't part of a dive trip. All other dives simply belong to the "previous" dive trip - i.e. the dive trip with the latest start time that is earlier or equal to the start time of this dive. This logic significantly simplifies the tracking of trips compared to other approaches that I have tried. The automatic grouping into trips now is an option that defaults to off (as it makes changes to the XML file - and people who don't want this feature shouldn't have trips added to their XML files that they then need to manually remove). For now you have to select this option, then exit the program and start it again. Still to do is to trigger the trip generation at run time. We also need a way to mark dives as not part of trips and to allow options to combine trips, split trips, edit trip location data, etc. The code has only had some limited testing when opening multiple files. The code is known to fail if a location name contains unquoted special characters like an "'". This commit also fixes a visual inconsistency in the preferences dialog where the font selector button didn't have a frame around it that told you what this option was about. Inspired-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-08-22 05:04:24 +00:00
/* save the dives */
for_each_dive(i, dive) {
Allow overlapping (and disjoint) dive trips We used to have the rule that a dive trip has to have all dives in it in sequential order, even though our XML file really is much more flexible, and allows arbitrary nesting of dives within a dive trip. Put another way, the old model had fairly inflexible rules: - the dive array is sorted by time - a dive trip is always a contiguous slice of this sorted array which makes perfect sense when you think of the dive and trip list as a physical activity by one person, but leads to various very subtle issues in the general case when there are no guarantees that the user then uses subsurface that way. In particular, if you load the XML files of two divers that have overlapping dive trips, the end result is incredibly messy, and does not conform to the above model at all. There's two ways to enforce such conformance: - disallow that kind of behavior entirely. This is actually hard. Our XML files aren't date-based, they are based on XML nesting rules, and even a single XML file can have nesting that violates the date ordering. With multiple XML files, it's trivial to do in practice, and while we could just fail at loading, the failure would have to be a hard failure that leaves the user no way to use the data at all. - try to "fix it up" by sorting, splitting, and combining dive trips automatically. Dirk had a patch to do this, but it really does destroy the actual dive data: if you load both mine and Dirk's dive trips, you ended up with a result that followed the above two technical rules, but that didn't actually make any *sense*. So this patch doesn't try to enforce the rules, and instead just changes them to be more generic: - the dive array is still sorted by dive time - a dive trip is just an arbitrary collection of dives. The relaxed rules means that mixing dives and dive trips for two people is trivial, and we can easily handle any XML file. The dive trip is defined by the XML nesting level, and is totally independent of any date-based sorting. It does require a few things: - when we save our dive data, we have to do it hierarchically by dive trip, not just by walking the dive array linearly. - similarly, when we create the dive tree model, we can't just blindly walk the array of dives one by one, we have to look up the correct trip (parent) - when we try to merge two dives that are adjacent (by date sorting), we can't do it if they are in different trips. but apart from that, nothing else really changes. NOTE! Despite the new relaxed model, creating totally disjoing dive trips is not all that easy (nor is there any *reason* for it to be easty). Our GUI interfaces still are "add dive to trip above" etc, and the automatic adding of dives to dive trips is obviously still based on date. So this does not really change the expected normal usage, the relaxed data structure rules just mean that we don't need to worry about the odd cases as much, because we can just let them be. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-30 19:00:37 +00:00
if (select_only) {
if(!dive->selected)
continue;
save_one_dive(b, dive);
Allow overlapping (and disjoint) dive trips We used to have the rule that a dive trip has to have all dives in it in sequential order, even though our XML file really is much more flexible, and allows arbitrary nesting of dives within a dive trip. Put another way, the old model had fairly inflexible rules: - the dive array is sorted by time - a dive trip is always a contiguous slice of this sorted array which makes perfect sense when you think of the dive and trip list as a physical activity by one person, but leads to various very subtle issues in the general case when there are no guarantees that the user then uses subsurface that way. In particular, if you load the XML files of two divers that have overlapping dive trips, the end result is incredibly messy, and does not conform to the above model at all. There's two ways to enforce such conformance: - disallow that kind of behavior entirely. This is actually hard. Our XML files aren't date-based, they are based on XML nesting rules, and even a single XML file can have nesting that violates the date ordering. With multiple XML files, it's trivial to do in practice, and while we could just fail at loading, the failure would have to be a hard failure that leaves the user no way to use the data at all. - try to "fix it up" by sorting, splitting, and combining dive trips automatically. Dirk had a patch to do this, but it really does destroy the actual dive data: if you load both mine and Dirk's dive trips, you ended up with a result that followed the above two technical rules, but that didn't actually make any *sense*. So this patch doesn't try to enforce the rules, and instead just changes them to be more generic: - the dive array is still sorted by dive time - a dive trip is just an arbitrary collection of dives. The relaxed rules means that mixing dives and dive trips for two people is trivial, and we can easily handle any XML file. The dive trip is defined by the XML nesting level, and is totally independent of any date-based sorting. It does require a few things: - when we save our dive data, we have to do it hierarchically by dive trip, not just by walking the dive array linearly. - similarly, when we create the dive tree model, we can't just blindly walk the array of dives one by one, we have to look up the correct trip (parent) - when we try to merge two dives that are adjacent (by date sorting), we can't do it if they are in different trips. but apart from that, nothing else really changes. NOTE! Despite the new relaxed model, creating totally disjoing dive trips is not all that easy (nor is there any *reason* for it to be easty). Our GUI interfaces still are "add dive to trip above" etc, and the automatic adding of dives to dive trips is obviously still based on date. So this does not really change the expected normal usage, the relaxed data structure rules just mean that we don't need to worry about the odd cases as much, because we can just let them be. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-30 19:00:37 +00:00
} else {
trip = dive->divetrip;
/* Bare dive without a trip? */
if (!trip) {
save_one_dive(b, dive);
continue;
}
Allow overlapping (and disjoint) dive trips We used to have the rule that a dive trip has to have all dives in it in sequential order, even though our XML file really is much more flexible, and allows arbitrary nesting of dives within a dive trip. Put another way, the old model had fairly inflexible rules: - the dive array is sorted by time - a dive trip is always a contiguous slice of this sorted array which makes perfect sense when you think of the dive and trip list as a physical activity by one person, but leads to various very subtle issues in the general case when there are no guarantees that the user then uses subsurface that way. In particular, if you load the XML files of two divers that have overlapping dive trips, the end result is incredibly messy, and does not conform to the above model at all. There's two ways to enforce such conformance: - disallow that kind of behavior entirely. This is actually hard. Our XML files aren't date-based, they are based on XML nesting rules, and even a single XML file can have nesting that violates the date ordering. With multiple XML files, it's trivial to do in practice, and while we could just fail at loading, the failure would have to be a hard failure that leaves the user no way to use the data at all. - try to "fix it up" by sorting, splitting, and combining dive trips automatically. Dirk had a patch to do this, but it really does destroy the actual dive data: if you load both mine and Dirk's dive trips, you ended up with a result that followed the above two technical rules, but that didn't actually make any *sense*. So this patch doesn't try to enforce the rules, and instead just changes them to be more generic: - the dive array is still sorted by dive time - a dive trip is just an arbitrary collection of dives. The relaxed rules means that mixing dives and dive trips for two people is trivial, and we can easily handle any XML file. The dive trip is defined by the XML nesting level, and is totally independent of any date-based sorting. It does require a few things: - when we save our dive data, we have to do it hierarchically by dive trip, not just by walking the dive array linearly. - similarly, when we create the dive tree model, we can't just blindly walk the array of dives one by one, we have to look up the correct trip (parent) - when we try to merge two dives that are adjacent (by date sorting), we can't do it if they are in different trips. but apart from that, nothing else really changes. NOTE! Despite the new relaxed model, creating totally disjoing dive trips is not all that easy (nor is there any *reason* for it to be easty). Our GUI interfaces still are "add dive to trip above" etc, and the automatic adding of dives to dive trips is obviously still based on date. So this does not really change the expected normal usage, the relaxed data structure rules just mean that we don't need to worry about the odd cases as much, because we can just let them be. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-12-30 19:00:37 +00:00
/* Have we already seen this trip (and thus saved this dive?) */
if (trip->index)
continue;
/* We haven't seen this trip before - save it and all dives */
trip->index = 1;
save_trip(b, trip);
}
}
put_format(b, "</dives>\n</divelog>\n");
}
void save_dives_logic(const char *filename, const bool select_only)
{
struct membuffer buf = {0};
FILE *f;
save_dives_buffer(&buf, select_only);
f = subsurface_fopen(filename, "w");
if (f) {
flush_buffer(&buf, f);
fclose(f);
}
free_buffer(&buf);
}
void export_dives_uddf(const char *filename, const bool selected)
{
FILE *f;
struct membuffer buf = {0};
xmlDoc *doc;
xsltStylesheetPtr xslt = NULL;
xmlDoc *transformed;
if (!filename)
return;
/* Save XML to file and convert it into a memory buffer */
save_dives_buffer(&buf, selected);
/*
* Parse the memory buffer into XML document and
* transform it to UDDF format, finally dumping
* the XML into a character buffer.
*/
doc = xmlReadMemory(buf.buffer, buf.used, "divelog", NULL, 0);
free_buffer(&buf);
if (!doc) {
fprintf(stderr, "Failed to read XML memory\n");
return;
}
/* Convert to UDDF format */
xslt = get_stylesheet("uddf-export.xslt");
if (!xslt) {
fprintf(stderr, "Failed to open UDDF conversion stylesheet\n");
return;
}
transformed = xsltApplyStylesheet(xslt, doc, NULL);
xsltFreeStylesheet(xslt);
xmlFreeDoc(doc);
/* Write the transformed XML to file */
f = subsurface_fopen(filename, "w");
xmlDocFormatDump(f, transformed, 1);
xmlFreeDoc(transformed);
fclose(f);
}