]> git.street.me.uk Git - andy/viking.git/blob - src/file.c
Allow setting timestamps on trackpoints and waypoints that previously had none.
[andy/viking.git] / src / file.c
1 /*
2  * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
3  *
4  * Copyright (C) 2003-2005, Evan Battaglia <gtoevan@gmx.net>
5  * Copyright (C) 2012, Guilhem Bonnefille <guilhem.bonnefille@gmail.com>
6  * Copyright (C) 2012-2013, Rob Norris <rw_norris@hotmail.com>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  *
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27 #include "viking.h"
28
29 #include "jpg.h"
30 #include "gpx.h"
31 #include "geojson.h"
32 #include "babel.h"
33
34 #include <string.h>
35 #include <stdlib.h>
36 #include <stdio.h>
37 #ifdef HAVE_UNISTD_H
38 #include <unistd.h>
39 #endif
40 #ifdef WINDOWS
41 #define realpath(X,Y) _fullpath(Y,X,MAX_PATH)
42 #endif
43 #include <glib.h>
44 #include <glib/gstdio.h>
45 #include <glib/gi18n.h>
46
47 #include "file.h"
48
49 #define TEST_BOOLEAN(str) (! ((str)[0] == '\0' || (str)[0] == '0' || (str)[0] == 'n' || (str)[0] == 'N' || (str)[0] == 'f' || (str)[0] == 'F') )
50 #define VIK_MAGIC "#VIK"
51 #define GPX_MAGIC "<?xm"
52 #define VIK_MAGIC_LEN 4
53
54 #define VIKING_FILE_VERSION 1
55
56 typedef struct _Stack Stack;
57
58 struct _Stack {
59   Stack *under;
60   gpointer *data;
61 };
62
63 static void pop(Stack **stack) {
64   Stack *tmp = (*stack)->under;
65   g_free ( *stack );
66   *stack = tmp;
67 }
68
69 static void push(Stack **stack)
70 {
71   Stack *tmp = g_malloc ( sizeof ( Stack ) );
72   tmp->under = *stack;
73   *stack = tmp;
74 }
75
76 static gboolean check_magic ( FILE *f, const gchar *magic_number )
77 {
78   gchar magic[VIK_MAGIC_LEN];
79   gboolean rv = FALSE;
80   gint8 i;
81   if ( fread(magic, 1, sizeof(magic), f) == sizeof(magic) &&
82       strncmp(magic, magic_number, sizeof(magic)) == 0 )
83     rv = TRUE;
84   for ( i = sizeof(magic)-1; i >= 0; i-- ) /* the ol' pushback */
85     ungetc(magic[i],f);
86   return rv;
87 }
88
89
90 static gboolean str_starts_with ( const gchar *haystack, const gchar *needle, guint16 len_needle, gboolean must_be_longer )
91 {
92   if ( strlen(haystack) > len_needle - (!must_be_longer) && strncasecmp ( haystack, needle, len_needle ) == 0 )
93     return TRUE;
94   return FALSE;
95 }
96
97 void file_write_layer_param ( FILE *f, const gchar *name, VikLayerParamType type, VikLayerParamData data ) {
98       /* string lists are handled differently. We get a GList (that shouldn't
99        * be freed) back for get_param and if it is null we shouldn't write
100        * anything at all (otherwise we'd read in a list with an empty string,
101        * not an empty string list.
102        */
103       if ( type == VIK_LAYER_PARAM_STRING_LIST ) {
104         if ( data.sl ) {
105           GList *iter = (GList *)data.sl;
106           while ( iter ) {
107             fprintf ( f, "%s=", name );
108             fprintf ( f, "%s\n", (gchar *)(iter->data) );
109             iter = iter->next;
110           }
111         }
112       } else {
113         fprintf ( f, "%s=", name );
114         switch ( type )
115         {
116           case VIK_LAYER_PARAM_DOUBLE: {
117   //          char buf[15]; /* locale independent */
118   //          fprintf ( f, "%s\n", (char *) g_dtostr (data.d, buf, sizeof (buf)) ); break;
119               fprintf ( f, "%f\n", data.d );
120               break;
121          }
122           case VIK_LAYER_PARAM_UINT: fprintf ( f, "%d\n", data.u ); break;
123           case VIK_LAYER_PARAM_INT: fprintf ( f, "%d\n", data.i ); break;
124           case VIK_LAYER_PARAM_BOOLEAN: fprintf ( f, "%c\n", data.b ? 't' : 'f' ); break;
125           case VIK_LAYER_PARAM_STRING: fprintf ( f, "%s\n", data.s ? data.s : "" ); break;
126           case VIK_LAYER_PARAM_COLOR: fprintf ( f, "#%.2x%.2x%.2x\n", (int)(data.c.red/256),(int)(data.c.green/256),(int)(data.c.blue/256)); break;
127           default: break;
128         }
129       }
130 }
131
132 static void write_layer_params_and_data ( VikLayer *l, FILE *f )
133 {
134   VikLayerParam *params = vik_layer_get_interface(l->type)->params;
135   VikLayerFuncGetParam get_param = vik_layer_get_interface(l->type)->get_param;
136
137   fprintf ( f, "name=%s\n", l->name ? l->name : "" );
138   if ( !l->visible )
139     fprintf ( f, "visible=f\n" );
140
141   if ( params && get_param )
142   {
143     VikLayerParamData data;
144     guint16 i, params_count = vik_layer_get_interface(l->type)->params_count;
145     for ( i = 0; i < params_count; i++ )
146     {
147       data = get_param(l, i, TRUE);
148       file_write_layer_param(f, params[i].name, params[i].type, data);
149     }
150   }
151   if ( vik_layer_get_interface(l->type)->write_file_data )
152   {
153     fprintf ( f, "\n\n~LayerData\n" );
154     vik_layer_get_interface(l->type)->write_file_data ( l, f );
155     fprintf ( f, "~EndLayerData\n" );
156   }
157   /* foreach param:
158      write param, and get_value, etc.
159      then run layer data, and that's it.
160   */
161 }
162
163 static void file_write ( VikAggregateLayer *top, FILE *f, gpointer vp )
164 {
165   Stack *stack = NULL;
166   VikLayer *current_layer;
167   struct LatLon ll;
168   VikViewportDrawMode mode;
169   gchar *modestring = NULL;
170
171   push(&stack);
172   stack->data = (gpointer) vik_aggregate_layer_get_children(VIK_AGGREGATE_LAYER(top));
173   stack->under = NULL;
174
175   /* crazhy CRAZHY */
176   vik_coord_to_latlon ( vik_viewport_get_center ( VIK_VIEWPORT(vp) ), &ll );
177
178   mode = vik_viewport_get_drawmode ( VIK_VIEWPORT(vp) );
179   switch ( mode ) {
180     case VIK_VIEWPORT_DRAWMODE_UTM: modestring = "utm"; break;
181     case VIK_VIEWPORT_DRAWMODE_EXPEDIA: modestring = "expedia"; break;
182     case VIK_VIEWPORT_DRAWMODE_MERCATOR: modestring = "mercator"; break;
183     case VIK_VIEWPORT_DRAWMODE_LATLON: modestring = "latlon"; break;
184     default:
185       g_critical("Houston, we've had a problem. mode=%d", mode);
186   }
187
188   fprintf ( f, "#VIKING GPS Data file " VIKING_URL "\n" );
189   fprintf ( f, "FILE_VERSION=%d\n", VIKING_FILE_VERSION );
190   fprintf ( f, "\nxmpp=%f\nympp=%f\nlat=%f\nlon=%f\nmode=%s\ncolor=%s\nhighlightcolor=%s\ndrawscale=%s\ndrawcentermark=%s\ndrawhighlight=%s\n",
191       vik_viewport_get_xmpp ( VIK_VIEWPORT(vp) ), vik_viewport_get_ympp ( VIK_VIEWPORT(vp) ), ll.lat, ll.lon,
192       modestring, vik_viewport_get_background_color(VIK_VIEWPORT(vp)),
193       vik_viewport_get_highlight_color(VIK_VIEWPORT(vp)),
194       vik_viewport_get_draw_scale(VIK_VIEWPORT(vp)) ? "t" : "f",
195       vik_viewport_get_draw_centermark(VIK_VIEWPORT(vp)) ? "t" : "f",
196       vik_viewport_get_draw_highlight(VIK_VIEWPORT(vp)) ? "t" : "f" );
197
198   if ( ! VIK_LAYER(top)->visible )
199     fprintf ( f, "visible=f\n" );
200
201   while (stack && stack->data)
202   {
203     current_layer = VIK_LAYER(((GList *)stack->data)->data);
204     fprintf ( f, "\n~Layer %s\n", vik_layer_get_interface(current_layer->type)->fixed_layer_name );
205     write_layer_params_and_data ( current_layer, f );
206     if ( current_layer->type == VIK_LAYER_AGGREGATE && !vik_aggregate_layer_is_empty(VIK_AGGREGATE_LAYER(current_layer)) )
207     {
208       push(&stack);
209       stack->data = (gpointer) vik_aggregate_layer_get_children(VIK_AGGREGATE_LAYER(current_layer));
210     }
211     else if ( current_layer->type == VIK_LAYER_GPS && !vik_gps_layer_is_empty(VIK_GPS_LAYER(current_layer)) )
212     {
213       push(&stack);
214       stack->data = (gpointer) vik_gps_layer_get_children(VIK_GPS_LAYER(current_layer));
215     }
216     else
217     {
218       stack->data = (gpointer) ((GList *)stack->data)->next;
219       fprintf ( f, "~EndLayer\n\n" );
220       while ( stack && (!stack->data) )
221       {
222         pop(&stack);
223         if ( stack )
224         {
225           stack->data = (gpointer) ((GList *)stack->data)->next;
226           fprintf ( f, "~EndLayer\n\n" );
227         }
228       }
229     }
230   }
231 /*
232   get vikaggregatelayer's children (?)
233   foreach write ALL params,
234   then layer data (IF function exists)
235   then endlayer
236
237   impl:
238   stack of layers (LIST) we are working on
239   when layer->next == NULL ...
240   we move on.
241 */
242 }
243
244 static void string_list_delete ( gpointer key, gpointer l, gpointer user_data )
245 {
246   /* 20071021 bugfix */
247   GList *iter = (GList *) l;
248   while ( iter ) {
249     g_free ( iter->data );
250     iter = iter->next;
251   }
252   g_list_free ( (GList *) l );
253 }
254
255 static void string_list_set_param (gint i, GList *list, gpointer *layer_and_vp)
256 {
257   VikLayerParamData x;
258   x.sl = list;
259   vik_layer_set_param ( VIK_LAYER(layer_and_vp[0]), i, x, layer_and_vp[1], TRUE );
260 }
261
262 /**
263  * Read in a Viking file and return how successful the parsing was
264  * ATM this will always work, in that even if there are parsing problems
265  *  then there will be no new values to override the defaults
266  *
267  * TODO flow up line number(s) / error messages of problems encountered...
268  *
269  */
270 static gboolean file_read ( VikAggregateLayer *top, FILE *f, const gchar *dirpath, VikViewport *vp )
271 {
272   Stack *stack;
273   struct LatLon ll = { 0.0, 0.0 };
274   gchar buffer[4096];
275   gchar *line;
276   guint16 len;
277   long line_num = 0;
278
279   VikLayerParam *params = NULL; /* for current layer, so we don't have to keep on looking up interface */
280   guint8 params_count = 0;
281
282   GHashTable *string_lists = g_hash_table_new(g_direct_hash,g_direct_equal);
283
284   gboolean successful_read = TRUE;
285
286   push(&stack);
287   stack->under = NULL;
288   stack->data = (gpointer) top;
289
290   while ( fgets ( buffer, 4096, f ) )
291   {
292     line_num++;
293
294     line = buffer;
295     while ( *line == ' ' || *line =='\t' )
296       line++;
297
298     if ( line[0] == '#' )
299       continue;
300     
301
302     len = strlen(line);
303     if ( len > 0 && line[len-1] == '\n' )
304       line[--len] = '\0';
305     if ( len > 0 && line[len-1] == '\r' )
306       line[--len] = '\0';
307
308     if ( len == 0 )
309       continue;
310
311
312     if ( line[0] == '~' )
313     {
314       line++; len--;
315       if ( *line == '\0' )
316         continue;
317       else if ( str_starts_with ( line, "Layer ", 6, TRUE ) )
318       {
319         int parent_type = VIK_LAYER(stack->data)->type;
320         if ( ( ! stack->data ) || ((parent_type != VIK_LAYER_AGGREGATE) && (parent_type != VIK_LAYER_GPS)) )
321         {
322           successful_read = FALSE;
323           g_warning ( "Line %ld: Layer command inside non-Aggregate Layer (type %d)", line_num, parent_type );
324           push(&stack); /* inside INVALID layer */
325           stack->data = NULL;
326           continue;
327         }
328         else
329         {
330           VikLayerTypeEnum type = vik_layer_type_from_string ( line+6 );
331           push(&stack);
332           if ( type == VIK_LAYER_NUM_TYPES )
333           {
334             successful_read = FALSE;
335             g_warning ( "Line %ld: Unknown type %s", line_num, line+6 );
336             stack->data = NULL;
337           }
338           else if (parent_type == VIK_LAYER_GPS)
339           {
340             stack->data = (gpointer) vik_gps_layer_get_a_child(VIK_GPS_LAYER(stack->under->data));
341             params = vik_layer_get_interface(type)->params;
342             params_count = vik_layer_get_interface(type)->params_count;
343           }
344           else
345           {
346             stack->data = (gpointer) vik_layer_create ( type, vp, FALSE );
347             params = vik_layer_get_interface(type)->params;
348             params_count = vik_layer_get_interface(type)->params_count;
349           }
350         }
351       }
352       else if ( str_starts_with ( line, "EndLayer", 8, FALSE ) )
353       {
354         if ( stack->under == NULL ) {
355           successful_read = FALSE;
356           g_warning ( "Line %ld: Mismatched ~EndLayer command", line_num );
357         }
358         else
359         {
360           /* add any string lists we've accumulated */
361           gpointer layer_and_vp[2];
362           layer_and_vp[0] = stack->data;
363           layer_and_vp[1] = vp;
364           g_hash_table_foreach ( string_lists, (GHFunc) string_list_set_param, layer_and_vp );
365           g_hash_table_remove_all ( string_lists );
366
367           if ( stack->data && stack->under->data )
368           {
369             if (VIK_LAYER(stack->under->data)->type == VIK_LAYER_AGGREGATE) {
370               vik_aggregate_layer_add_layer ( VIK_AGGREGATE_LAYER(stack->under->data), VIK_LAYER(stack->data), FALSE );
371               vik_layer_post_read ( VIK_LAYER(stack->data), vp, TRUE );
372             }
373             else if (VIK_LAYER(stack->under->data)->type == VIK_LAYER_GPS) {
374               /* TODO: anything else needs to be done here ? */
375             }
376             else {
377               successful_read = FALSE;
378               g_warning ( "Line %ld: EndLayer command inside non-Aggregate Layer (type %d)", line_num, VIK_LAYER(stack->data)->type );
379             }
380           }
381           pop(&stack);
382         }
383       }
384       else if ( str_starts_with ( line, "LayerData", 9, FALSE ) )
385       {
386         if ( stack->data && vik_layer_get_interface(VIK_LAYER(stack->data)->type)->read_file_data )
387         {
388           /* must read until hits ~EndLayerData */
389           if ( ! vik_layer_get_interface(VIK_LAYER(stack->data)->type)->read_file_data ( VIK_LAYER(stack->data), f, dirpath ) )
390             successful_read = FALSE;
391         }
392         else
393         { /* simply skip layer data over */
394           while ( fgets ( buffer, 4096, f ) )
395           {
396             line_num++;
397
398             line = buffer;
399
400             len = strlen(line);
401             if ( len > 0 && line[len-1] == '\n' )
402               line[--len] = '\0';
403             if ( len > 0 && line[len-1] == '\r' )
404               line[--len] = '\0';
405             if ( strcasecmp ( line, "~EndLayerData" ) == 0 )
406               break;
407           }
408           continue;
409         }
410       }
411       else
412       {
413         successful_read = FALSE;
414         g_warning ( "Line %ld: Unknown tilde command", line_num );
415       }
416     }
417     else
418     {
419       gint32 eq_pos = -1;
420       guint16 i;
421       if ( ! stack->data )
422         continue;
423
424       for ( i = 0; i < len; i++ )
425         if ( line[i] == '=' )
426           eq_pos = i;
427
428       if ( stack->under == NULL && eq_pos == 12 && strncasecmp ( line, "FILE_VERSION", eq_pos ) == 0) {
429         gint version = strtol(line+13, NULL, 10);
430         g_debug ( "%s: reading file version %d", __FUNCTION__, version );
431         if ( version > VIKING_FILE_VERSION )
432           successful_read = FALSE;
433         // However we'll still carry and attempt to read whatever we can
434       }
435       else if ( stack->under == NULL && eq_pos == 4 && strncasecmp ( line, "xmpp", eq_pos ) == 0) /* "hard coded" params: global & for all layer-types */
436         vik_viewport_set_xmpp ( VIK_VIEWPORT(vp), strtod ( line+5, NULL ) );
437       else if ( stack->under == NULL && eq_pos == 4 && strncasecmp ( line, "ympp", eq_pos ) == 0)
438         vik_viewport_set_ympp ( VIK_VIEWPORT(vp), strtod ( line+5, NULL ) );
439       else if ( stack->under == NULL && eq_pos == 3 && strncasecmp ( line, "lat", eq_pos ) == 0 )
440         ll.lat = strtod ( line+4, NULL );
441       else if ( stack->under == NULL && eq_pos == 3 && strncasecmp ( line, "lon", eq_pos ) == 0 )
442         ll.lon = strtod ( line+4, NULL );
443       else if ( stack->under == NULL && eq_pos == 4 && strncasecmp ( line, "mode", eq_pos ) == 0 && strcasecmp ( line+5, "utm" ) == 0)
444         vik_viewport_set_drawmode ( VIK_VIEWPORT(vp), VIK_VIEWPORT_DRAWMODE_UTM);
445       else if ( stack->under == NULL && eq_pos == 4 && strncasecmp ( line, "mode", eq_pos ) == 0 && strcasecmp ( line+5, "expedia" ) == 0)
446         vik_viewport_set_drawmode ( VIK_VIEWPORT(vp), VIK_VIEWPORT_DRAWMODE_EXPEDIA );
447       else if ( stack->under == NULL && eq_pos == 4 && strncasecmp ( line, "mode", eq_pos ) == 0 && strcasecmp ( line+5, "google" ) == 0)
448       {
449         successful_read = FALSE;
450         g_warning ( _("Draw mode '%s' no more supported"), "google" );
451       }
452       else if ( stack->under == NULL && eq_pos == 4 && strncasecmp ( line, "mode", eq_pos ) == 0 && strcasecmp ( line+5, "kh" ) == 0)
453       {
454         successful_read = FALSE;
455         g_warning ( _("Draw mode '%s' no more supported"), "kh" );
456       }
457       else if ( stack->under == NULL && eq_pos == 4 && strncasecmp ( line, "mode", eq_pos ) == 0 && strcasecmp ( line+5, "mercator" ) == 0)
458         vik_viewport_set_drawmode ( VIK_VIEWPORT(vp), VIK_VIEWPORT_DRAWMODE_MERCATOR );
459       else if ( stack->under == NULL && eq_pos == 4 && strncasecmp ( line, "mode", eq_pos ) == 0 && strcasecmp ( line+5, "latlon" ) == 0)
460         vik_viewport_set_drawmode ( VIK_VIEWPORT(vp), VIK_VIEWPORT_DRAWMODE_LATLON );
461       else if ( stack->under == NULL && eq_pos == 5 && strncasecmp ( line, "color", eq_pos ) == 0 )
462         vik_viewport_set_background_color ( VIK_VIEWPORT(vp), line+6 );
463       else if ( stack->under == NULL && eq_pos == 14 && strncasecmp ( line, "highlightcolor", eq_pos ) == 0 )
464         vik_viewport_set_highlight_color ( VIK_VIEWPORT(vp), line+15 );
465       else if ( stack->under == NULL && eq_pos == 9 && strncasecmp ( line, "drawscale", eq_pos ) == 0 )
466         vik_viewport_set_draw_scale ( VIK_VIEWPORT(vp), TEST_BOOLEAN(line+10) );
467       else if ( stack->under == NULL && eq_pos == 14 && strncasecmp ( line, "drawcentermark", eq_pos ) == 0 )
468         vik_viewport_set_draw_centermark ( VIK_VIEWPORT(vp), TEST_BOOLEAN(line+15) );
469       else if ( stack->under == NULL && eq_pos == 13 && strncasecmp ( line, "drawhighlight", eq_pos ) == 0 )
470         vik_viewport_set_draw_highlight ( VIK_VIEWPORT(vp), TEST_BOOLEAN(line+14) );
471       else if ( stack->under && eq_pos == 4 && strncasecmp ( line, "name", eq_pos ) == 0 )
472         vik_layer_rename ( VIK_LAYER(stack->data), line+5 );
473       else if ( eq_pos == 7 && strncasecmp ( line, "visible", eq_pos ) == 0 )
474         VIK_LAYER(stack->data)->visible = TEST_BOOLEAN(line+8);
475       else if ( eq_pos != -1 && stack->under )
476       {
477         gboolean found_match = FALSE;
478
479         /* go thru layer params. if len == eq_pos && starts_with jazz, set it. */
480         /* also got to check for name and visible. */
481
482         if ( ! params )
483         {
484           successful_read = FALSE;
485           g_warning ( "Line %ld: No options for this kind of layer", line_num );
486           continue;
487         }
488
489         for ( i = 0; i < params_count; i++ )
490           if ( strlen(params[i].name) == eq_pos && strncasecmp ( line, params[i].name, eq_pos ) == 0 )
491           {
492             VikLayerParamData x;
493             line += eq_pos+1;
494             if ( params[i].type == VIK_LAYER_PARAM_STRING_LIST ) {
495               GList *l = g_list_append ( g_hash_table_lookup ( string_lists, GINT_TO_POINTER ((gint) i) ), 
496                                          g_strdup(line) );
497               g_hash_table_replace ( string_lists, GINT_TO_POINTER ((gint)i), l );
498               /* add the value to a list, possibly making a new list.
499                * this will be passed to the layer when we read an ~EndLayer */
500             } else {
501               switch ( params[i].type )
502               {
503                 case VIK_LAYER_PARAM_DOUBLE: x.d = strtod(line, NULL); break;
504                 case VIK_LAYER_PARAM_UINT: x.u = strtoul(line, NULL, 10); break;
505                 case VIK_LAYER_PARAM_INT: x.i = strtol(line, NULL, 10); break;
506                 case VIK_LAYER_PARAM_BOOLEAN: x.b = TEST_BOOLEAN(line); break;
507                 case VIK_LAYER_PARAM_COLOR: memset(&(x.c), 0, sizeof(x.c)); /* default: black */
508                                           gdk_color_parse ( line, &(x.c) ); break;
509                 /* STRING or STRING_LIST -- if STRING_LIST, just set param to add a STRING */
510                 default: x.s = line;
511               }
512               vik_layer_set_param ( VIK_LAYER(stack->data), i, x, vp, TRUE );
513             }
514             found_match = TRUE;
515             break;
516           }
517         if ( ! found_match ) {
518           // ATM don't flow up this issue because at least one internal parameter has changed from version 1.3
519           //   and don't what to worry users about raising such issues
520           // TODO Maybe hold old values here - compare the line value against them and if a match
521           //       generate a different style of message in the GUI...
522           // successful_read = FALSE;
523           g_warning ( "Line %ld: Unknown parameter. Line:\n%s", line_num, line );
524         }
525       }
526       else {
527         successful_read = FALSE;
528         g_warning ( "Line %ld: Invalid parameter or parameter outside of layer.", line_num );
529       }
530     }
531 /* could be:
532 [Layer Type=Bla]
533 [EndLayer]
534 [LayerData]
535 name=this
536 #comment
537 */
538   }
539
540   while ( stack )
541   {
542     if ( stack->under && stack->under->data && stack->data )
543     {
544       vik_aggregate_layer_add_layer ( VIK_AGGREGATE_LAYER(stack->under->data), VIK_LAYER(stack->data), FALSE );
545       vik_layer_post_read ( VIK_LAYER(stack->data), vp, TRUE );
546     }
547     pop(&stack);
548   }
549
550   if ( ll.lat != 0.0 || ll.lon != 0.0 )
551     vik_viewport_set_center_latlon ( VIK_VIEWPORT(vp), &ll, TRUE );
552
553   if ( ( ! VIK_LAYER(top)->visible ) && VIK_LAYER(top)->realized )
554     vik_treeview_item_set_visible ( VIK_LAYER(top)->vt, &(VIK_LAYER(top)->iter), FALSE ); 
555
556   /* delete anything we've forgotten about -- should only happen when file ends before an EndLayer */
557   g_hash_table_foreach ( string_lists, string_list_delete, NULL );
558   g_hash_table_destroy ( string_lists );
559
560   return successful_read;
561 }
562
563 /*
564 read thru file
565 if "[Layer Type="
566   push(&stack)
567   new default layer of type (str_to_type) (check interface->name)
568 if "[EndLayer]"
569   VikLayer *vl = stack->data;
570   pop(&stack);
571   vik_aggregate_layer_add_layer(stack->data, vl);
572 if "[LayerData]"
573   vik_layer_data ( VIK_LAYER_DATA(stack->data), f, vp );
574
575 */
576
577 /* ---------------------------------------------------- */
578
579 static FILE *xfopen ( const char *fn )
580 {
581   if ( strcmp(fn,"-") == 0 )
582     return stdin;
583   else
584     return g_fopen(fn, "r");
585 }
586
587 static void xfclose ( FILE *f )
588 {
589   if ( f != stdin && f != stdout ) {
590     fclose ( f );
591     f = NULL;
592   }
593 }
594
595 /*
596  * Function to determine if a filename is a 'viking' type file
597  */
598 gboolean check_file_magic_vik ( const gchar *filename )
599 {
600   gboolean result = FALSE;
601   FILE *ff = xfopen ( filename );
602   if ( ff ) {
603     result = check_magic ( ff, VIK_MAGIC );
604     xfclose ( ff );
605   }
606   return result;
607 }
608
609 /**
610  * append_file_ext:
611  *
612  * Append a file extension, if not already present.
613  *
614  * Returns: a newly allocated string
615  */
616 gchar *append_file_ext ( const gchar *filename, VikFileType_t type )
617 {
618   gchar *new_name = NULL;
619   const gchar *ext = NULL;
620
621   /* Select an extension */
622   switch (type)
623   {
624   case FILE_TYPE_GPX:
625     ext = ".gpx";
626     break;
627   case FILE_TYPE_KML:
628     ext = ".kml";
629     break;
630   case FILE_TYPE_GEOJSON:
631     ext = ".geojson";
632     break;
633   case FILE_TYPE_GPSMAPPER:
634   case FILE_TYPE_GPSPOINT:
635   default:
636     /* Do nothing, ext already set to NULL */
637     break;
638   }
639
640   /* Do */
641   if ( ext != NULL && ! a_file_check_ext ( filename, ext ) )
642     new_name = g_strconcat ( filename, ext, NULL );
643   else
644     /* Simply duplicate */
645     new_name = g_strdup ( filename );
646
647   return new_name;
648 }
649
650 VikLoadType_t a_file_load ( VikAggregateLayer *top, VikViewport *vp, const gchar *filename_or_uri )
651 {
652   g_return_val_if_fail ( vp != NULL, LOAD_TYPE_READ_FAILURE );
653
654   char *filename = (char *)filename_or_uri;
655   if (strncmp(filename, "file://", 7) == 0) {
656     // Consider replacing this with:
657     // filename = g_filename_from_uri ( entry, NULL, NULL );
658     // Since this doesn't support URIs properly (i.e. will failure if is it has %20 characters in it)
659     filename = filename + 7;
660     g_debug ( "Loading file %s from URI %s", filename, filename_or_uri );
661   }
662   FILE *f = xfopen ( filename );
663
664   if ( ! f )
665     return LOAD_TYPE_READ_FAILURE;
666
667   VikLoadType_t load_answer = LOAD_TYPE_OTHER_SUCCESS;
668
669   gchar *dirpath = g_path_get_dirname ( filename );
670   // Attempt loading the primary file type first - our internal .vik file:
671   if ( check_magic ( f, VIK_MAGIC ) )
672   {
673     if ( file_read ( top, f, dirpath, vp ) )
674       load_answer = LOAD_TYPE_VIK_SUCCESS;
675     else
676       load_answer = LOAD_TYPE_VIK_FAILURE_NON_FATAL;
677   }
678   else if ( a_jpg_magic_check ( filename ) ) {
679     if ( ! a_jpg_load_file ( top, filename, vp ) )
680       load_answer = LOAD_TYPE_UNSUPPORTED_FAILURE;
681   }
682   else
683   {
684         // For all other file types which consist of tracks, routes and/or waypoints,
685         //  must be loaded into a new TrackWaypoint layer (hence it be created)
686     gboolean success = TRUE; // Detect load failures - mainly to remove the layer created as it's not required
687
688     VikLayer *vtl = vik_layer_create ( VIK_LAYER_TRW, vp, FALSE );
689     vik_layer_rename ( vtl, a_file_basename ( filename ) );
690
691     // In fact both kml & gpx files start the same as they are in xml
692     if ( a_file_check_ext ( filename, ".kml" ) && check_magic ( f, GPX_MAGIC ) ) {
693       // Implicit Conversion
694       if ( ! ( success = a_babel_convert_from ( VIK_TRW_LAYER(vtl), "-i kml", filename, NULL, NULL, NULL ) ) ) {
695         load_answer = LOAD_TYPE_GPSBABEL_FAILURE;
696       }
697     }
698     // NB use a extension check first, as a GPX file header may have a Byte Order Mark (BOM) in it
699     //    - which currently confuses our check_magic function
700     else if ( a_file_check_ext ( filename, ".gpx" ) || check_magic ( f, GPX_MAGIC ) ) {
701       if ( ! ( success = a_gpx_read_file ( VIK_TRW_LAYER(vtl), f ) ) ) {
702         load_answer = LOAD_TYPE_GPX_FAILURE;
703       }
704     }
705     else {
706       // Try final supported file type
707       if ( ! ( success = a_gpspoint_read_file ( VIK_TRW_LAYER(vtl), f, dirpath ) ) ) {
708         // Failure here means we don't know how to handle the file
709         load_answer = LOAD_TYPE_UNSUPPORTED_FAILURE;
710       }
711     }
712     g_free ( dirpath );
713
714     // Clean up when we can't handle the file
715     if ( ! success ) {
716       // free up layer
717       g_object_unref ( vtl );
718     }
719     else {
720       // Complete the setup from the successful load
721       vik_layer_post_read ( vtl, vp, TRUE );
722       vik_aggregate_layer_add_layer ( top, vtl, FALSE );
723       vik_trw_layer_auto_set_view ( VIK_TRW_LAYER(vtl), vp );
724     }
725   }
726   xfclose(f);
727   return load_answer;
728 }
729
730 gboolean a_file_save ( VikAggregateLayer *top, gpointer vp, const gchar *filename )
731 {
732   FILE *f;
733
734   if (strncmp(filename, "file://", 7) == 0)
735     filename = filename + 7;
736
737   f = g_fopen(filename, "w");
738
739   if ( ! f )
740     return FALSE;
741
742   // Enable relative paths in .vik files to work
743   gchar *cwd = g_get_current_dir();
744   gchar *dir = g_path_get_dirname ( filename );
745   if ( dir ) {
746     if ( g_chdir ( dir ) ) {
747       g_warning ( "Could not change directory to %s", dir );
748     }
749     g_free (dir);
750   }
751
752   file_write ( top, f, vp );
753
754   // Restore previous working directory
755   if ( cwd ) {
756     if ( g_chdir ( cwd ) ) {
757       g_warning ( "Could not return to directory %s", cwd );
758     }
759     g_free (cwd);
760   }
761
762   fclose(f);
763   f = NULL;
764
765   return TRUE;
766 }
767
768
769 /* example: 
770      gboolean is_gpx = a_file_check_ext ( "a/b/c.gpx", ".gpx" );
771 */
772 gboolean a_file_check_ext ( const gchar *filename, const gchar *fileext )
773 {
774   g_return_val_if_fail ( filename != NULL, FALSE );
775   g_return_val_if_fail ( fileext && fileext[0]=='.', FALSE );
776   const gchar *basename = a_file_basename(filename);
777   if (!basename)
778     return FALSE;
779
780   const char * dot = strrchr(basename, '.');
781   if (dot && !strcmp(dot, fileext))
782     return TRUE;
783
784   return FALSE;
785 }
786
787 /**
788  * a_file_export:
789  * @vtl: The TrackWaypoint to export data from
790  * @filename: The name of the file to be written
791  * @file_type: Choose one of the supported file types for the export
792  * @trk: If specified then only export this track rather than the whole layer
793  * @write_hidden: Whether to write invisible items
794  *
795  * A general export command to convert from Viking TRW layer data to an external supported format.
796  * The write_hidden option is provided mainly to be able to transfer selected items when uploading to a GPS
797  */
798 gboolean a_file_export ( VikTrwLayer *vtl, const gchar *filename, VikFileType_t file_type, VikTrack *trk, gboolean write_hidden )
799 {
800   GpxWritingOptions options = { FALSE, FALSE, write_hidden, FALSE };
801   FILE *f = g_fopen ( filename, "w" );
802   if ( f )
803   {
804     gboolean result = TRUE;
805
806     if ( trk ) {
807       switch ( file_type ) {
808         case FILE_TYPE_GPX:
809           // trk defined so can set the option
810           options.is_route = trk->is_route;
811           a_gpx_write_track_file ( trk, f, &options );
812           break;
813         default:
814           g_critical("Houston, we've had a problem. file_type=%d", file_type);
815       }
816     } else {
817       switch ( file_type ) {
818         case FILE_TYPE_GPSMAPPER:
819           a_gpsmapper_write_file ( vtl, f );
820           break;
821         case FILE_TYPE_GPX:
822           a_gpx_write_file ( vtl, f, &options );
823           break;
824         case FILE_TYPE_GPSPOINT:
825           a_gpspoint_write_file ( vtl, f );
826           break;
827         case FILE_TYPE_GEOJSON:
828           result = a_geojson_write_file ( vtl, f );
829           break;
830         case FILE_TYPE_KML:
831           fclose ( f );
832           f = NULL;
833           switch ( a_vik_get_kml_export_units () ) {
834             case VIK_KML_EXPORT_UNITS_STATUTE:
835               return a_babel_convert_to ( vtl, NULL, "-o kml", filename, NULL, NULL );
836               break;
837             case VIK_KML_EXPORT_UNITS_NAUTICAL:
838               return a_babel_convert_to ( vtl, NULL, "-o kml,units=n", filename, NULL, NULL );
839               break;
840             default:
841               // VIK_KML_EXPORT_UNITS_METRIC:
842               return a_babel_convert_to ( vtl, NULL, "-o kml,units=m", filename, NULL, NULL );
843               break;
844           }
845           break;
846         default:
847           g_critical("Houston, we've had a problem. file_type=%d", file_type);
848       }
849     }
850     fclose ( f );
851     f = NULL;
852     return result;
853   }
854   return FALSE;
855 }
856
857 /**
858  * a_file_export_babel:
859  */
860 gboolean a_file_export_babel ( VikTrwLayer *vtl, const gchar *filename, const gchar *format,
861                                gboolean tracks, gboolean routes, gboolean waypoints )
862 {
863   gchar *args = g_strdup_printf("%s %s %s -o %s",
864                                 tracks ? "-t" : "",
865                                 routes ? "-r" : "",
866                                 waypoints ? "-w" : "",
867                                 format);
868   gboolean result = a_babel_convert_to ( vtl, NULL, args, filename, NULL, NULL );
869   g_free(args);
870   return result;
871 }
872
873 /**
874  * Just a wrapper around realpath, which itself is platform dependent
875  */
876 char *file_realpath ( const char *path, char *real )
877 {
878   return realpath ( path, real );
879 }
880
881 #ifndef MAXPATHLEN
882 #define MAXPATHLEN 1024
883 #endif
884 /**
885  * Always return the canonical filename in a newly allocated string
886  */
887 char *file_realpath_dup ( const char *path )
888 {
889         char real[MAXPATHLEN];
890
891         g_return_val_if_fail(path != NULL, NULL);
892
893         if (file_realpath(path, real))
894                 return g_strdup(real);
895
896         return g_strdup(path);
897 }
898
899 /**
900  * Permission granted to use this code after personal correspondance
901  * Slightly reworked for better cross platform use, glibisms, function rename and a compacter format
902  *
903  * FROM http://www.codeguru.com/cpp/misc/misc/fileanddirectorynaming/article.php/c263
904  */
905
906 // GetRelativeFilename(), by Rob Fisher.
907 // rfisher@iee.org
908 // http://come.to/robfisher
909
910 // The number of characters at the start of an absolute filename.  e.g. in DOS,
911 // absolute filenames start with "X:\" so this value should be 3, in UNIX they start
912 // with "\" so this value should be 1.
913 #ifdef WINDOWS
914 #define ABSOLUTE_NAME_START 3
915 #else
916 #define ABSOLUTE_NAME_START 1
917 #endif
918
919 // Given the absolute current directory and an absolute file name, returns a relative file name.
920 // For example, if the current directory is C:\foo\bar and the filename C:\foo\whee\text.txt is given,
921 // GetRelativeFilename will return ..\whee\text.txt.
922
923 const gchar *file_GetRelativeFilename ( gchar *currentDirectory, gchar *absoluteFilename )
924 {
925   gint afMarker = 0, rfMarker = 0;
926   gint cdLen = 0, afLen = 0;
927   gint i = 0;
928   gint levels = 0;
929   static gchar relativeFilename[MAXPATHLEN+1];
930
931   cdLen = strlen(currentDirectory);
932   afLen = strlen(absoluteFilename);
933
934   // make sure the names are not too long or too short
935   if (cdLen > MAXPATHLEN || cdLen < ABSOLUTE_NAME_START+1 ||
936       afLen > MAXPATHLEN || afLen < ABSOLUTE_NAME_START+1) {
937     return NULL;
938   }
939
940   // Handle DOS names that are on different drives:
941   if (currentDirectory[0] != absoluteFilename[0]) {
942     // not on the same drive, so only absolute filename will do
943     strcpy(relativeFilename, absoluteFilename);
944     return relativeFilename;
945   }
946
947   // they are on the same drive, find out how much of the current directory
948   // is in the absolute filename
949   i = ABSOLUTE_NAME_START;
950   while (i < afLen && i < cdLen && currentDirectory[i] == absoluteFilename[i]) {
951     i++;
952   }
953
954   if (i == cdLen && (absoluteFilename[i] == G_DIR_SEPARATOR || absoluteFilename[i-1] == G_DIR_SEPARATOR)) {
955     // the whole current directory name is in the file name,
956     // so we just trim off the current directory name to get the
957     // current file name.
958     if (absoluteFilename[i] == G_DIR_SEPARATOR) {
959       // a directory name might have a trailing slash but a relative
960       // file name should not have a leading one...
961       i++;
962     }
963
964     strcpy(relativeFilename, &absoluteFilename[i]);
965     return relativeFilename;
966   }
967
968   // The file is not in a child directory of the current directory, so we
969   // need to step back the appropriate number of parent directories by
970   // using "..\"s.  First find out how many levels deeper we are than the
971   // common directory
972   afMarker = i;
973   levels = 1;
974
975   // count the number of directory levels we have to go up to get to the
976   // common directory
977   while (i < cdLen) {
978     i++;
979     if (currentDirectory[i] == G_DIR_SEPARATOR) {
980       // make sure it's not a trailing slash
981       i++;
982       if (currentDirectory[i] != '\0') {
983         levels++;
984       }
985     }
986   }
987
988   // move the absolute filename marker back to the start of the directory name
989   // that it has stopped in.
990   while (afMarker > 0 && absoluteFilename[afMarker-1] != G_DIR_SEPARATOR) {
991     afMarker--;
992   }
993
994   // check that the result will not be too long
995   if (levels * 3 + afLen - afMarker > MAXPATHLEN) {
996     return NULL;
997   }
998
999   // add the appropriate number of "..\"s.
1000   rfMarker = 0;
1001   for (i = 0; i < levels; i++) {
1002     relativeFilename[rfMarker++] = '.';
1003     relativeFilename[rfMarker++] = '.';
1004     relativeFilename[rfMarker++] = G_DIR_SEPARATOR;
1005   }
1006
1007   // copy the rest of the filename into the result string
1008   strcpy(&relativeFilename[rfMarker], &absoluteFilename[afMarker]);
1009
1010   return relativeFilename;
1011 }
1012 /* END http://www.codeguru.com/cpp/misc/misc/fileanddirectorynaming/article.php/c263 */