subsurface/save-xml.c

711 lines
19 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>
Initial implementation of git save format This saves the dive data into a git object repository instead of a single XML file. We create a git object tree with each dive as a separate file, hierarchically by trip and date. NOTE 1: This largely duplicates the XML saving code, because trying to share it seemed just too painful: the logic is very similar, but the details of the actual strings end up differing sufficiently that there are tons of trivial differences. The git save format is line-based with minimal quoting, while XML quotes everything with either "<..\>" or using single quotes around attributes. NOTE 2: You currently need a dummy "file" to save to, which points to the real save location: the git repository and branch to be used. We should make this a config thing, but for testing, do something like this: echo git /home/torvalds/scuba:linus > git-test to create that git information file, and when you use "Save To" and specify "git-test" as the file to save to, subsurface will use the new git save logic to save to the branch "linus" in the repository found at "/home/torvalds/scuba". NOTE 3: The git save format uses just the git object directory, it does *not* check out the result in any git working tree or index. So after you do a save, you can do git log -p linus to see what actually happened in that branch, but it will not affect any actual checked-out state in the repository. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-03-06 21:28:39 +00:00
#include <fcntl.h>
#include "dive.h"
#include "device.h"
#include "membuffer.h"
#include "strndup.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)
{
int is_html = 0;
put_quoted(b, text, is_attribute, is_html);
}
static void show_utf8(struct membuffer *b, const char *text, const char *pre, const char *post, int is_attribute)
{
int len;
char *cleaned;
if (!text)
return;
/* remove leading and trailing space */
/* We need to combine isascii() with isspace(),
* because we can only trust isspace() with 7-bit ascii,
* on windows for example */
while (isascii(*text) && isspace(*text))
text++;
len = strlen(text);
if (!len)
return;
while (len && isascii(text[len - 1]) && isspace(text[len - 1]))
len--;
cleaned = strndup(text, len);
put_string(b, pre);
quote(b, cleaned, is_attribute);
put_string(b, post);
free(cleaned);
}
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");
}
static void save_overview(struct membuffer *b, struct dive *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);
}
static void put_gasmix(struct membuffer *b, struct gasmix *mix)
{
int o2 = mix->o2.permille;
int he = mix->he.permille;
if (o2) {
put_format(b, " o2='%u.%u%%'", FRACTION(o2, 10));
if (he)
put_format(b, " he='%u.%u%%'", FRACTION(he, 10));
}
}
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;
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);
put_gasmix(b, &cylinder->gasmix);
put_pressure(b, cylinder->start, " start='", " bar'");
put_pressure(b, cylinder->end, " end='", " bar'");
if (cylinder->cylinder_use != OC_GAS)
show_utf8(b, cylinderuse_text[cylinder->cylinder_use], " use='", "'", 1);
put_format(b, " />\n");
}
}
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'");
if (sample->temperature.mkelvin && sample->temperature.mkelvin != old->temperature.mkelvin) {
put_temperature(b, sample->temperature, " temp='", " C'");
old->temperature = sample->temperature;
}
put_pressure(b, sample->cylinderpressure, " pressure='", " bar'");
put_pressure(b, sample->o2cylinderpressure, " o2pressure='", " 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->tts.seconds != old->tts.seconds) {
put_format(b, " tts='%u:%02u min'", FRACTION(sample->tts.seconds, 60));
old->tts = sample->tts;
}
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->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->o2sensor[0].mbar) && (sample->o2sensor[0].mbar != old->o2sensor[0].mbar)) {
put_milli(b, " sensor1='", sample->o2sensor[0].mbar, " bar'");
old->o2sensor[0] = sample->o2sensor[0];
}
if ((sample->o2sensor[1].mbar) && (sample->o2sensor[1].mbar != old->o2sensor[1].mbar)) {
put_milli(b, " sensor2='", sample->o2sensor[1].mbar, " bar'");
old->o2sensor[1] = sample->o2sensor[1];
}
if ((sample->o2sensor[2].mbar) && (sample->o2sensor[2].mbar != old->o2sensor[2].mbar)) {
put_milli(b, " sensor3='", sample->o2sensor[2].mbar, " bar'");
old->o2sensor[2] = sample->o2sensor[2];
}
if (sample->setpoint.mbar != old->setpoint.mbar) {
put_milli(b, " po2='", sample->setpoint.mbar, " bar'");
old->setpoint = sample->setpoint;
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
}
show_index(b, sample->heartbeat, "heartbeat='", "'");
show_index(b, sample->bearing.degrees, "bearing='", "'");
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);
if (event_is_gaschange(ev)) {
if (ev->gas.index >= 0) {
show_index(b, ev->gas.index, "cylinder='", "'");
put_gasmix(b, &ev->gas.mix);
} else if (!event_gasmix_redundant(ev))
put_gasmix(b, &ev->gas.mix);
}
put_format(b, " />\n");
}
static void save_events(struct membuffer *b, struct event *ev)
{
while (ev) {
save_one_event(b, ev);
ev = ev->next;
}
}
Get rid of crazy empty tag_list element at the start So this is totally unrelated to the git repository format, except for the fact that I noticed it while writing the git saving code. The subsurface divetag list handling is being stupid, and has a initial dummy entry at the head of the list for no good reason. I say "no good reason", because there *is* a reason for it: it allows code to avoid the special case of empty list and adding entries to before the first entry etc etc. But that reason is a really *bad* reason, because it's valid only because people don't understand basic list manipulation and pointers to pointers. So get rid of the dummy element, and do things right instead - by passing a *pointer* to the list, instead of the list. And then when traversing the list and looking for a place to insert things, don't go to the next entry - just update the "pointer to pointer" to point to the address of the next entry. Each entry in a C linked list is no different than the list itself, so you can use the pointer to the pointer to the next entry as a pointer to the list. This is a pet peeve of mine. The real beauty of pointers can never be understood unless you understand the indirection they allow. People who grew up with Pascal and were corrupted by that mindset are mentally stunted. Niklaus Wirth has a lot to answer for! But never fear. You too can overcome that mental limitation, it just needs some brain exercise. Reading this patch may help. In particular, contemplate the new "taglist_add_divetag()". Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-03-10 17:18:13 +00:00
static void save_tags(struct membuffer *b, struct tag_entry *entry)
{
Get rid of crazy empty tag_list element at the start So this is totally unrelated to the git repository format, except for the fact that I noticed it while writing the git saving code. The subsurface divetag list handling is being stupid, and has a initial dummy entry at the head of the list for no good reason. I say "no good reason", because there *is* a reason for it: it allows code to avoid the special case of empty list and adding entries to before the first entry etc etc. But that reason is a really *bad* reason, because it's valid only because people don't understand basic list manipulation and pointers to pointers. So get rid of the dummy element, and do things right instead - by passing a *pointer* to the list, instead of the list. And then when traversing the list and looking for a place to insert things, don't go to the next entry - just update the "pointer to pointer" to point to the address of the next entry. Each entry in a C linked list is no different than the list itself, so you can use the pointer to the pointer to the next entry as a pointer to the list. This is a pet peeve of mine. The real beauty of pointers can never be understood unless you understand the indirection they allow. People who grew up with Pascal and were corrupted by that mindset are mentally stunted. Niklaus Wirth has a lot to answer for! But never fear. You too can overcome that mental limitation, it just needs some brain exercise. Reading this patch may help. In particular, contemplate the new "taglist_add_divetag()". Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-03-10 17:18:13 +00:00
if (entry) {
const char *sep = " tags='";
do {
struct divetag *tag = entry->tag;
put_string(b, sep);
/* If the tag has been translated, write the source to the xml file */
quote(b, tag->source ?: tag->name, 1);
Get rid of crazy empty tag_list element at the start So this is totally unrelated to the git repository format, except for the fact that I noticed it while writing the git saving code. The subsurface divetag list handling is being stupid, and has a initial dummy entry at the head of the list for no good reason. I say "no good reason", because there *is* a reason for it: it allows code to avoid the special case of empty list and adding entries to before the first entry etc etc. But that reason is a really *bad* reason, because it's valid only because people don't understand basic list manipulation and pointers to pointers. So get rid of the dummy element, and do things right instead - by passing a *pointer* to the list, instead of the list. And then when traversing the list and looking for a place to insert things, don't go to the next entry - just update the "pointer to pointer" to point to the address of the next entry. Each entry in a C linked list is no different than the list itself, so you can use the pointer to the pointer to the next entry as a pointer to the list. This is a pet peeve of mine. The real beauty of pointers can never be understood unless you understand the indirection they allow. People who grew up with Pascal and were corrupted by that mindset are mentally stunted. Niklaus Wirth has a lot to answer for! But never fear. You too can overcome that mental limitation, it just needs some brain exercise. Reading this patch may help. In particular, contemplate the new "taglist_add_divetag()". Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-03-10 17:18:13 +00:00
sep = ", ";
} while ((entry = entry->next) != NULL);
put_string(b, "'");
}
}
static void save_extra_data(struct membuffer *b, struct extra_data *ed)
{
while (ed) {
if (ed->key && ed->value) {
put_string(b, " <extradata");
show_utf8(b, ed->key, " key='", "'", 1);
show_utf8(b, ed->value, " value='", "'", 1);
put_string(b, " />\n");
}
ed = ed->next;
}
}
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)
{
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'");
if (dc->divemode != OC) {
for (enum dive_comp_type i = 0; i < NUM_DC_TYPE; i++)
if (dc->divemode == i)
show_utf8(b, divemode_text[i], " dctype='", "'", 1);
if (dc->no_o2sensors)
put_format(b," no_o2sensors='%d'", dc->no_o2sensors);
}
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_extra_data(b, dc->extra_data);
save_events(b, dc->events);
save_samples(b, dc->samples, dc->sample);
put_format(b, " </divecomputer>\n");
}
extern char * hashstring(char * filename);
static void save_picture(struct membuffer *b, struct picture *pic)
{
put_string(b, " <picture filename='");
put_quoted(b, pic->filename, true, false);
put_string(b, "'");
if (pic->offset.seconds) {
int offset = pic->offset.seconds;
char sign = '+';
if (offset < 0) {
sign = '-';
offset = -offset;
}
put_format(b, " offset='%c%u:%02u min'", sign, FRACTION(offset, 60));
}
if (pic->latitude.udeg || pic->longitude.udeg) {
put_degrees(b, pic->latitude, " gps='", " ");
put_degrees(b, pic->longitude, "", "'");
}
if (hashstring(pic->filename))
put_format(b, " hash='%s'", hashstring(pic->filename));
put_string(b, "/>\n");
}
void save_one_dive_to_mb(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);
Get rid of crazy empty tag_list element at the start So this is totally unrelated to the git repository format, except for the fact that I noticed it while writing the git saving code. The subsurface divetag list handling is being stupid, and has a initial dummy entry at the head of the list for no good reason. I say "no good reason", because there *is* a reason for it: it allows code to avoid the special case of empty list and adding entries to before the first entry etc etc. But that reason is a really *bad* reason, because it's valid only because people don't understand basic list manipulation and pointers to pointers. So get rid of the dummy element, and do things right instead - by passing a *pointer* to the list, instead of the list. And then when traversing the list and looking for a place to insert things, don't go to the next entry - just update the "pointer to pointer" to point to the address of the next entry. Each entry in a C linked list is no different than the list itself, so you can use the pointer to the pointer to the next entry as a pointer to the list. This is a pet peeve of mine. The real beauty of pointers can never be understood unless you understand the indirection they allow. People who grew up with Pascal and were corrupted by that mindset are mentally stunted. Niklaus Wirth has a lot to answer for! But never fear. You too can overcome that mental limitation, it just needs some brain exercise. Reading this patch may help. In particular, contemplate the new "taglist_add_divetag()". Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-03-10 17:18:13 +00:00
save_tags(b, dive->tag_list);
if (dive->dive_site_uuid)
put_format(b, " divesiteid='%8x'", dive->dive_site_uuid);
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 */
for_each_dc(dive, dc)
save_dc(b, dive, dc);
FOR_EACH_PICTURE(dive)
save_picture(b, picture);
put_format(b, "</dive>\n");
}
int save_dive(FILE *f, struct dive *dive)
{
struct membuffer buf = { 0 };
save_one_dive_to_mb(&buf, dive);
flush_buffer(&buf, f);
/* Error handling? */
return 0;
}
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_to_mb(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 3
int save_dives(const char *filename)
{
return 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);
if (prefs.save_userid_local)
put_format(b, " <userid>%30s</userid>\n", prefs.userid);
/* 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");
/* save the dive sites */
put_format(b, "<divesites>\n");
for (i = 0; i < dive_site_table.nr; i++) {
struct dive_site *ds = get_dive_site(i);
if (dive_site_is_empty(ds)) {
int j;
struct dive *d;
for_each_dive(j, d) {
if (d->dive_site_uuid == ds->uuid)
d->dive_site_uuid = 0;
}
delete_dive_site(get_dive_site(i)->uuid);
i--; // since we just deleted that one
continue;
}
put_format(b, "<site uuid='%8x'", ds->uuid);
show_utf8(b, ds->name, " name='", "'", 1);
if (ds->latitude.udeg || ds->longitude.udeg) {
put_degrees(b, ds->latitude, " gps='", " ");
put_degrees(b, ds->longitude, "", "'");
}
show_utf8(b, ds->description, " description='", "'", 1);
show_utf8(b, ds->notes, " notes='", "'", 1);
put_format(b, "/>\n");
}
put_format(b, "</divesites>\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) {
if (select_only) {
if (!dive->selected)
continue;
save_one_dive_to_mb(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_to_mb(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");
}
static void save_backup(const char *name, const char *ext, const char *new_ext)
{
int len = strlen(name);
int a = strlen(ext), b = strlen(new_ext);
char *newname;
/* len up to and including the final '.' */
len -= a;
if (len <= 1)
return;
if (name[len - 1] != '.')
return;
/* msvc doesn't have strncasecmp, has _strnicmp instead - crazy */
if (strncasecmp(name + len, ext, a))
return;
newname = malloc(len + b + 1);
if (!newname)
return;
memcpy(newname, name, len);
memcpy(newname + len, new_ext, b + 1);
/*
* Ignore errors. Maybe we can't create the backup file,
* maybe no old file existed. Regardless, we'll write the
* new file.
*/
(void) subsurface_rename(name, newname);
free(newname);
}
Initial implementation of git save format This saves the dive data into a git object repository instead of a single XML file. We create a git object tree with each dive as a separate file, hierarchically by trip and date. NOTE 1: This largely duplicates the XML saving code, because trying to share it seemed just too painful: the logic is very similar, but the details of the actual strings end up differing sufficiently that there are tons of trivial differences. The git save format is line-based with minimal quoting, while XML quotes everything with either "<..\>" or using single quotes around attributes. NOTE 2: You currently need a dummy "file" to save to, which points to the real save location: the git repository and branch to be used. We should make this a config thing, but for testing, do something like this: echo git /home/torvalds/scuba:linus > git-test to create that git information file, and when you use "Save To" and specify "git-test" as the file to save to, subsurface will use the new git save logic to save to the branch "linus" in the repository found at "/home/torvalds/scuba". NOTE 3: The git save format uses just the git object directory, it does *not* check out the result in any git working tree or index. So after you do a save, you can do git log -p linus to see what actually happened in that branch, but it will not affect any actual checked-out state in the repository. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-03-06 21:28:39 +00:00
static void try_to_backup(const char *filename)
{
char extension[][5] = { "xml", "ssrf", "" };
int i = 0;
int flen = strlen(filename);
/* Maybe we might want to make this configurable? */
while (extension[i][0] != '\0') {
int elen = strlen(extension[i]);
if (strcasecmp(filename + flen - elen, extension[i]) == 0) {
if (last_xml_version < VERSION) {
int se_len = strlen(extension[i]) + 5;
char *special_ext = malloc(se_len);
snprintf(special_ext, se_len, "%s.v%d", extension[i], last_xml_version);
save_backup(filename, extension[i], special_ext);
free(special_ext);
} else {
save_backup(filename, extension[i], "bak");
}
break;
}
i++;
}
Initial implementation of git save format This saves the dive data into a git object repository instead of a single XML file. We create a git object tree with each dive as a separate file, hierarchically by trip and date. NOTE 1: This largely duplicates the XML saving code, because trying to share it seemed just too painful: the logic is very similar, but the details of the actual strings end up differing sufficiently that there are tons of trivial differences. The git save format is line-based with minimal quoting, while XML quotes everything with either "<..\>" or using single quotes around attributes. NOTE 2: You currently need a dummy "file" to save to, which points to the real save location: the git repository and branch to be used. We should make this a config thing, but for testing, do something like this: echo git /home/torvalds/scuba:linus > git-test to create that git information file, and when you use "Save To" and specify "git-test" as the file to save to, subsurface will use the new git save logic to save to the branch "linus" in the repository found at "/home/torvalds/scuba". NOTE 3: The git save format uses just the git object directory, it does *not* check out the result in any git working tree or index. So after you do a save, you can do git log -p linus to see what actually happened in that branch, but it will not affect any actual checked-out state in the repository. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-03-06 21:28:39 +00:00
}
int save_dives_logic(const char *filename, const bool select_only)
Initial implementation of git save format This saves the dive data into a git object repository instead of a single XML file. We create a git object tree with each dive as a separate file, hierarchically by trip and date. NOTE 1: This largely duplicates the XML saving code, because trying to share it seemed just too painful: the logic is very similar, but the details of the actual strings end up differing sufficiently that there are tons of trivial differences. The git save format is line-based with minimal quoting, while XML quotes everything with either "<..\>" or using single quotes around attributes. NOTE 2: You currently need a dummy "file" to save to, which points to the real save location: the git repository and branch to be used. We should make this a config thing, but for testing, do something like this: echo git /home/torvalds/scuba:linus > git-test to create that git information file, and when you use "Save To" and specify "git-test" as the file to save to, subsurface will use the new git save logic to save to the branch "linus" in the repository found at "/home/torvalds/scuba". NOTE 3: The git save format uses just the git object directory, it does *not* check out the result in any git working tree or index. So after you do a save, you can do git log -p linus to see what actually happened in that branch, but it will not affect any actual checked-out state in the repository. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-03-06 21:28:39 +00:00
{
struct membuffer buf = { 0 };
FILE *f;
void *git;
const char *branch;
int error;
Initial implementation of git save format This saves the dive data into a git object repository instead of a single XML file. We create a git object tree with each dive as a separate file, hierarchically by trip and date. NOTE 1: This largely duplicates the XML saving code, because trying to share it seemed just too painful: the logic is very similar, but the details of the actual strings end up differing sufficiently that there are tons of trivial differences. The git save format is line-based with minimal quoting, while XML quotes everything with either "<..\>" or using single quotes around attributes. NOTE 2: You currently need a dummy "file" to save to, which points to the real save location: the git repository and branch to be used. We should make this a config thing, but for testing, do something like this: echo git /home/torvalds/scuba:linus > git-test to create that git information file, and when you use "Save To" and specify "git-test" as the file to save to, subsurface will use the new git save logic to save to the branch "linus" in the repository found at "/home/torvalds/scuba". NOTE 3: The git save format uses just the git object directory, it does *not* check out the result in any git working tree or index. So after you do a save, you can do git log -p linus to see what actually happened in that branch, but it will not affect any actual checked-out state in the repository. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-03-06 21:28:39 +00:00
git = is_git_repository(filename, &branch);
if (git)
return git_save_dives(git, branch, select_only);
try_to_backup(filename);
Initial implementation of git save format This saves the dive data into a git object repository instead of a single XML file. We create a git object tree with each dive as a separate file, hierarchically by trip and date. NOTE 1: This largely duplicates the XML saving code, because trying to share it seemed just too painful: the logic is very similar, but the details of the actual strings end up differing sufficiently that there are tons of trivial differences. The git save format is line-based with minimal quoting, while XML quotes everything with either "<..\>" or using single quotes around attributes. NOTE 2: You currently need a dummy "file" to save to, which points to the real save location: the git repository and branch to be used. We should make this a config thing, but for testing, do something like this: echo git /home/torvalds/scuba:linus > git-test to create that git information file, and when you use "Save To" and specify "git-test" as the file to save to, subsurface will use the new git save logic to save to the branch "linus" in the repository found at "/home/torvalds/scuba". NOTE 3: The git save format uses just the git object directory, it does *not* check out the result in any git working tree or index. So after you do a save, you can do git log -p linus to see what actually happened in that branch, but it will not affect any actual checked-out state in the repository. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2014-03-06 21:28:39 +00:00
save_dives_buffer(&buf, select_only);
error = -1;
f = subsurface_fopen(filename, "w");
if (f) {
flush_buffer(&buf, f);
error = fclose(f);
}
if (error)
report_error("Save failed (%s)", strerror(errno));
free_buffer(&buf);
return error;
}
int export_dives_xslt(const char *filename, const bool selected, const int units, const char *export_xslt)
{
FILE *f;
struct membuffer buf = { 0 };
xmlDoc *doc;
xsltStylesheetPtr xslt = NULL;
xmlDoc *transformed;
int res = 0;
char *params[3];
int pnr = 0;
char unitstr[3];
if (verbose)
fprintf(stderr, "export_dives_xslt with stylesheet %s\n", export_xslt);
if (!filename)
return report_error("No filename for export");
/* 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 selected export format, finally dumping
* the XML into a character buffer.
*/
doc = xmlReadMemory(buf.buffer, buf.len, "divelog", NULL, 0);
free_buffer(&buf);
if (!doc)
return report_error("Failed to read XML memory");
/* Convert to export format */
xslt = get_stylesheet(export_xslt);
if (!xslt)
return report_error("Failed to open export conversion stylesheet");
snprintf(unitstr, 3, "%d", units);
params[pnr++] = "units";
params[pnr++] = unitstr;
params[pnr++] = NULL;
transformed = xsltApplyStylesheet(xslt, doc, (const char **)params);
xmlFreeDoc(doc);
/* Write the transformed export to file */
f = subsurface_fopen(filename, "w");
if (f) {
xsltSaveResultToFile(f, transformed, xslt);
fclose(f);
/* Check write errors? */
} else {
res = report_error("Failed to open %s for writing (%s)", filename, strerror(errno));
}
xsltFreeStylesheet(xslt);
xmlFreeDoc(transformed);
return res;
}