Contents of /trunk/src/diff.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 4 - (show annotations)
Wed Dec 10 19:50:17 2008 UTC (15 years, 4 months ago) by harbaum
File MIME type: text/plain
File size: 19377 byte(s)
Asynchronous network io
1 /*
2 * Copyright (C) 2008 Till Harbaum <till@harbaum.org>.
3 *
4 * This file is part of OSM2Go.
5 *
6 * OSM2Go is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * OSM2Go is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with OSM2Go. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 /*
21 * diff.c - generate and restore changes on the current data set
22 */
23
24 #include "appdata.h"
25
26 #include <libxml/parser.h>
27 #include <libxml/tree.h>
28
29 #if !defined(LIBXML_TREE_ENABLED) || !defined(LIBXML_OUTPUT_ENABLED)
30 #error "libxml doesn't support required tree or output"
31 #endif
32
33 static void diff_save_tags(tag_t *tag, xmlNodePtr node) {
34 while(tag) {
35 xmlNodePtr tag_node = xmlNewChild(node, NULL,
36 BAD_CAST "tag", NULL);
37 xmlNewProp(tag_node, BAD_CAST "k", BAD_CAST tag->key);
38 xmlNewProp(tag_node, BAD_CAST "v", BAD_CAST tag->value);
39 tag = tag->next;
40 }
41 }
42
43 static void diff_save_state_n_id(int flags, xmlNodePtr node, item_id_t id) {
44 if(flags & OSM_FLAG_DELETED)
45 xmlNewProp(node, BAD_CAST "state", BAD_CAST "deleted");
46 else if(flags & OSM_FLAG_NEW)
47 xmlNewProp(node, BAD_CAST "state", BAD_CAST "new");
48
49 /* all items need an id */
50 char *id_str = g_strdup_printf("%ld", id);
51 xmlNewProp(node, BAD_CAST "id", BAD_CAST id_str);
52 g_free(id_str);
53 }
54
55 static void diff_save_nodes(node_t *node, xmlNodePtr root_node) {
56 /* store all modfied nodes */
57 while(node) {
58 if(node->flags) {
59 xmlNodePtr node_node = xmlNewChild(root_node, NULL,
60 BAD_CAST "node", NULL);
61
62 diff_save_state_n_id(node->flags, node_node, node->id);
63
64 if(!(node->flags & OSM_FLAG_DELETED)) {
65 char str[32];
66
67 /* additional info is only required if the node hasn't been deleted */
68 g_ascii_dtostr(str, sizeof(str), node->pos.lat);
69 xmlNewProp(node_node, BAD_CAST "lat", BAD_CAST str);
70 g_ascii_dtostr(str, sizeof(str), node->pos.lon);
71 xmlNewProp(node_node, BAD_CAST "lon", BAD_CAST str);
72 snprintf(str, sizeof(str), "%ld", node->time);
73 xmlNewProp(node_node, BAD_CAST "time", BAD_CAST str);
74
75 diff_save_tags(node->tag, node_node);
76 }
77 }
78 node = node->next;
79 }
80 }
81
82 static void diff_save_ways(way_t *way, xmlNodePtr root_node) {
83
84 /* store all modfied ways */
85 while(way) {
86 if(way->flags) {
87 xmlNodePtr node_way = xmlNewChild(root_node, NULL,
88 BAD_CAST "way", NULL);
89
90 diff_save_state_n_id(way->flags, node_way, way->id);
91
92 if(way->flags & OSM_FLAG_HIDDEN)
93 xmlNewProp(node_way, BAD_CAST "hidden", BAD_CAST "true");
94
95 /* additional info is only required if the way hasn't been deleted */
96 /* and of the dirty or new flags are set. (otherwise e.g. only */
97 /* the hidden flag may be set) */
98 if((!(way->flags & OSM_FLAG_DELETED)) &&
99 (way->flags & (OSM_FLAG_DIRTY | OSM_FLAG_NEW))) {
100 node_chain_t *node_chain = way->node_chain;
101 while(node_chain) {
102 xmlNodePtr node_node = xmlNewChild(node_way, NULL,
103 BAD_CAST "nd", NULL);
104 char *id = g_strdup_printf("%ld", node_chain->node->id);
105 xmlNewProp(node_node, BAD_CAST "ref", BAD_CAST id);
106 g_free(id);
107 node_chain = node_chain->next;
108 }
109 diff_save_tags(way->tag, node_way);
110 }
111 }
112 way = way->next;
113 }
114 }
115
116 static void diff_save_relations(relation_t *relation, xmlNodePtr root_node) {
117
118 /* store all modfied relations */
119 while(relation) {
120 if(relation->flags) {
121 xmlNodePtr node_rel = xmlNewChild(root_node, NULL,
122 BAD_CAST "relation", NULL);
123
124 diff_save_state_n_id(relation->flags, node_rel, relation->id);
125
126 if(!(relation->flags & OSM_FLAG_DELETED)) {
127 /* additional info is only required if the relation */
128 /* hasn't been deleted */
129 member_t *member = relation->member;
130 while(member) {
131 xmlNodePtr node_member = xmlNewChild(node_rel, NULL,
132 BAD_CAST "member", NULL);
133
134 char *ref = NULL;
135 switch(member->type) {
136 case NODE:
137 xmlNewProp(node_member, BAD_CAST "type", BAD_CAST "node");
138 ref = g_strdup_printf("%ld", member->node->id);
139 break;
140 case WAY:
141 xmlNewProp(node_member, BAD_CAST "type", BAD_CAST "way");
142 ref = g_strdup_printf("%ld", member->way->id);
143 break;
144 case RELATION:
145 xmlNewProp(node_member, BAD_CAST "type", BAD_CAST "relation");
146 ref = g_strdup_printf("%ld", member->relation->id);
147 break;
148
149 /* XXX_ID's are used if this is a reference to an item not */
150 /* stored in this xml data set */
151 case NODE_ID:
152 xmlNewProp(node_member, BAD_CAST "type", BAD_CAST "node");
153 ref = g_strdup_printf("%ld", member->id);
154 break;
155 case WAY_ID:
156 xmlNewProp(node_member, BAD_CAST "type", BAD_CAST "way");
157 ref = g_strdup_printf("%ld", member->id);
158 break;
159 case RELATION_ID:
160 xmlNewProp(node_member, BAD_CAST "type", BAD_CAST "relation");
161 ref = g_strdup_printf("%ld", member->id);
162 break;
163
164 default:
165 printf("unexpected member type %d\n", member->type);
166 break;
167 }
168
169 g_assert(ref);
170 xmlNewProp(node_member, BAD_CAST "ref", BAD_CAST ref);
171 g_free(ref);
172
173 if(member->role)
174 xmlNewProp(node_member, BAD_CAST "role", BAD_CAST member->role);
175
176 member = member->next;
177 }
178 diff_save_tags(relation->tag, node_rel);
179 }
180 }
181 relation = relation->next;
182 }
183 }
184
185
186 /* return true if no diff needs to be saved */
187 gboolean diff_is_clean(osm_t *osm, gboolean honor_hidden_flags) {
188 gboolean clean = TRUE;
189
190 /* check if a diff is necessary */
191 node_t *node = osm->node;
192 while(node && clean) {
193 if(node->flags) clean = FALSE;
194 node = node->next;
195 }
196
197 way_t *way = osm->way;
198 while(way && clean) {
199 if(honor_hidden_flags) {
200 if(way->flags) clean = FALSE;
201 } else
202 if(way->flags & ~OSM_FLAG_HIDDEN)
203 clean = FALSE;
204
205 way = way->next;
206 }
207
208 relation_t *relation = osm->relation;
209 while(relation && clean) {
210 if(relation->flags) clean = FALSE;
211 relation = relation->next;
212 }
213
214 return clean;
215 }
216
217 void diff_save(project_t *project, osm_t *osm) {
218 if(!project || !osm) return;
219
220 char *diff_name =
221 g_strdup_printf("%s/%s.diff", project->path, project->name);
222
223 if(diff_is_clean(osm, TRUE)) {
224 printf("data set is clean, removing diff if present\n");
225 g_remove(diff_name);
226 g_free(diff_name);
227 return;
228 }
229
230 printf("data set is dirty, generating diff\n");
231
232 LIBXML_TEST_VERSION;
233
234 xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0");
235 xmlNodePtr root_node = xmlNewNode(NULL, BAD_CAST "diff");
236 xmlNewProp(root_node, BAD_CAST "name", BAD_CAST project->name);
237 xmlDocSetRootElement(doc, root_node);
238
239 diff_save_nodes(osm->node, root_node);
240 diff_save_ways(osm->way, root_node);
241 diff_save_relations(osm->relation, root_node);
242
243 xmlSaveFormatFileEnc(diff_name, doc, "UTF-8", 1);
244 xmlFreeDoc(doc);
245 xmlCleanupParser();
246
247 g_free(diff_name);
248 }
249
250 static int xml_get_prop_int(xmlNode *node, char *prop, int def) {
251 char *str = (char*)xmlGetProp(node, BAD_CAST prop);
252 int value = def;
253
254 if(str) {
255 value = strtoul(str, NULL, 10);
256 xmlFree(str);
257 }
258
259 return value;
260 }
261
262 static int xml_get_prop_state(xmlNode *node, char *prop) {
263 char *str = (char*)xmlGetProp(node, BAD_CAST prop);
264
265 if(str) {
266 if(strcasecmp(str, "new") == 0) {
267 xmlFree(str);
268 return OSM_FLAG_NEW;
269 }
270
271 if(strcasecmp(str, "deleted") == 0) {
272 xmlFree(str);
273 return OSM_FLAG_DELETED;
274 }
275
276 g_assert(0);
277 }
278
279 return OSM_FLAG_DIRTY;
280 }
281
282 static pos_t *xml_get_prop_pos(xmlNode *node) {
283 char *str_lat = (char*)xmlGetProp(node, BAD_CAST "lat");
284 char *str_lon = (char*)xmlGetProp(node, BAD_CAST "lon");
285
286 if(!str_lon || !str_lat) {
287 if(!str_lon) xmlFree(str_lon);
288 if(!str_lat) xmlFree(str_lat);
289 return NULL;
290 }
291
292 pos_t *pos = g_new0(pos_t, 1);
293 pos->lat = g_ascii_strtod(str_lat, NULL);
294 pos->lon = g_ascii_strtod(str_lon, NULL);
295
296 xmlFree(str_lon);
297 xmlFree(str_lat);
298
299 return pos;
300 }
301
302 static tag_t *xml_scan_tags(xmlDoc *doc, xmlNodePtr node, osm_t *osm) {
303 /* scan for tags */
304 tag_t *first_tag = NULL;
305 tag_t **tag = &first_tag;
306
307 while(node) {
308 if(node->type == XML_ELEMENT_NODE) {
309 if(strcasecmp((char*)node->name, "tag") == 0) {
310 /* attach tag to node/way */
311 *tag = osm_parse_osm_tag(osm, doc, node);
312 if(*tag) tag = &((*tag)->next);
313 }
314 }
315 node = node->next;
316 }
317 return first_tag;
318 }
319
320 void diff_restore_node(xmlDoc *doc, xmlNodePtr node_node, osm_t *osm) {
321 printf("Restoring node\n");
322
323 /* read properties */
324 item_id_t id = xml_get_prop_int(node_node, "id", ID_ILLEGAL);
325 if(id == ID_ILLEGAL) {
326 printf(" Node entry missing id\n");
327 return;
328 }
329
330 int state = xml_get_prop_state(node_node, "state");
331 pos_t *pos = xml_get_prop_pos(node_node);
332
333 if(!(state & OSM_FLAG_DELETED) && !pos) {
334 printf(" Node not deleted, but no valid position\n");
335 return;
336 }
337
338 /* evaluate properties */
339 node_t *node = NULL;
340
341 switch(state) {
342 case OSM_FLAG_NEW:
343 printf(" Restoring NEW node\n");
344
345 node = g_new0(node_t, 1);
346 node->visible = TRUE;
347 node->flags = OSM_FLAG_NEW;
348 node->time = xml_get_prop_int(node_node, "time", 0);
349 if(!node->time) node->time = time(NULL);
350
351 /* attach to end of node list */
352 node_t **lnode = &osm->node;
353 while(*lnode) lnode = &(*lnode)->next;
354 *lnode = node;
355 break;
356
357 case OSM_FLAG_DELETED:
358 printf(" Restoring DELETE flag\n");
359
360 if((node = osm_get_node_by_id(osm, id)))
361 node->flags |= OSM_FLAG_DELETED;
362 else
363 printf(" WARNING: no node with that id found\n");
364 break;
365
366 case OSM_FLAG_DIRTY:
367 printf(" Valid id/position (DIRTY)\n");
368
369 if((node = osm_get_node_by_id(osm, id)))
370 node->flags |= OSM_FLAG_DIRTY;
371 else
372 printf(" WARNING: no node with that id found\n");
373 break;
374
375 default:
376 printf(" Illegal node entry\n");
377 return;
378 break;
379 }
380
381 if(!node) {
382 printf(" no valid node\n");
383 return;
384 }
385
386 /* update id and position from diff */
387 node->id = id;
388 if(pos) {
389 node->pos.lat = pos->lat;
390 node->pos.lon = pos->lon;
391
392 pos2lpos(osm->bounds, &node->pos, &node->lpos);
393
394 g_free(pos);
395 }
396
397 /* node may be an existing node, so remove tags to */
398 /* make space for new ones */
399 if(node->tag) {
400 printf(" removing existing tags for diff tags\n");
401 osm_tag_free(node->tag);
402 node->tag = NULL;
403 }
404
405 node->tag = xml_scan_tags(doc, node_node->children, osm);
406 }
407
408 void diff_restore_way(xmlDoc *doc, xmlNodePtr node_node, osm_t *osm) {
409 printf("Restoring way\n");
410
411 item_id_t id = xml_get_prop_int(node_node, "id", ID_ILLEGAL);
412 if(id == ID_ILLEGAL) {
413 printf(" entry missing id\n");
414 return;
415 }
416
417 int state = xml_get_prop_state(node_node, "state");
418
419 /* handle hidden flag */
420 gboolean hidden = FALSE;
421 char *str = (char*)xmlGetProp(node_node, BAD_CAST "hidden");
422 if(str) {
423 if(strcasecmp(str, "true") == 0)
424 hidden = TRUE;
425
426 xmlFree(str);
427 }
428
429
430 /* evaluate properties */
431 way_t *way = NULL;
432 switch(state) {
433 case OSM_FLAG_NEW:
434 printf(" Restoring NEW way\n");
435
436 way = g_new0(way_t, 1);
437 way->visible = TRUE;
438 way->flags = OSM_FLAG_NEW;
439 way->time = xml_get_prop_int(node_node, "time", 0);
440 if(!way->time) way->time = time(NULL);
441
442 /* attach to end of way list */
443 way_t **lway = &osm->way;
444 while(*lway) lway = &(*lway)->next;
445 *lway = way;
446 break;
447
448 case OSM_FLAG_DELETED:
449 printf(" Restoring DELETE flag\n");
450
451 if((way = osm_get_way_by_id(osm, id)))
452 way->flags |= OSM_FLAG_DELETED;
453 else
454 printf(" WARNING: no way with that id found\n");
455 break;
456
457 case OSM_FLAG_DIRTY:
458 printf(" Valid id (DIRTY)\n");
459
460 if((way = osm_get_way_by_id(osm, id)))
461 way->flags |= OSM_FLAG_DIRTY;
462 else
463 printf(" WARNING: no way with that id found\n");
464 break;
465
466 default:
467 printf(" Illegal way entry\n");
468 return;
469 }
470
471 if(!way) {
472 printf(" no valid way\n");
473 return;
474 }
475
476 /* update id from diff */
477 way->id = id;
478
479 /* update node_chain */
480 if(hidden)
481 way->flags |= OSM_FLAG_HIDDEN;
482
483 gboolean installed_new_nodes = FALSE;
484
485 /* scan for nodes */
486 node_chain_t **node_chain = &way->node_chain;
487 xmlNode *nd_node = NULL;
488 for(nd_node = node_node->children; nd_node; nd_node = nd_node->next) {
489 if(nd_node->type == XML_ELEMENT_NODE) {
490 if(strcasecmp((char*)nd_node->name, "nd") == 0) {
491
492 /* only replace the original nodes if new nodes have actually been */
493 /* found. */
494 if(!installed_new_nodes) {
495 /* way may be an existing way, so remove nodes to */
496 /* make space for new ones */
497 if(way->node_chain) {
498 printf(" removing existing nodes for diff nodes\n");
499 osm_node_chain_free(way->node_chain);
500 way->node_chain = NULL;
501 }
502
503 installed_new_nodes = TRUE;
504 }
505
506 /* attach node to node_chain */
507 *node_chain = osm_parse_osm_way_nd(osm, doc, nd_node);
508 if(*node_chain)
509 node_chain = &((*node_chain)->next);
510 }
511 }
512 }
513
514 /* only replace tags if nodes have been found before. if no nodes */
515 /* were found this wasn't a dirty entry but e.g. only the hidden */
516 /* flag had been set */
517 if(installed_new_nodes) {
518
519 /* node may be an existing node, so remove tags to */
520 /* make space for new ones */
521 if(way->tag) {
522 printf(" removing existing tags for diff tags\n");
523 osm_tag_free(way->tag);
524 way->tag = NULL;
525 }
526
527 way->tag = xml_scan_tags(doc, node_node->children, osm);
528 } else {
529 printf(" no nodes restored, way isn't dirty!\n");
530 way->flags &= ~OSM_FLAG_DIRTY;
531 }
532 }
533
534 void diff_restore_relation(xmlDoc *doc, xmlNodePtr node_rel, osm_t *osm) {
535 printf("Restoring relation\n");
536
537 item_id_t id = xml_get_prop_int(node_rel, "id", ID_ILLEGAL);
538 if(id == ID_ILLEGAL) {
539 printf(" entry missing id\n");
540 return;
541 }
542
543 int state = xml_get_prop_state(node_rel, "state");
544
545 /* evaluate properties */
546 relation_t *relation = NULL;
547 switch(state) {
548 case OSM_FLAG_NEW:
549 printf(" Restoring NEW relation\n");
550
551 relation = g_new0(relation_t, 1);
552 relation->visible = TRUE;
553 relation->flags = OSM_FLAG_NEW;
554 relation->time = xml_get_prop_int(node_rel, "time", 0);
555 if(!relation->time) relation->time = time(NULL);
556
557 /* attach to end of relation list */
558 relation_t **lrelation = &osm->relation;
559 while(*lrelation) lrelation = &(*lrelation)->next;
560 *lrelation = relation;
561 break;
562
563 case OSM_FLAG_DELETED:
564 printf(" Restoring DELETE flag\n");
565
566 if((relation = osm_get_relation_by_id(osm, id)))
567 relation->flags |= OSM_FLAG_DELETED;
568 else
569 printf(" WARNING: no relation with that id found\n");
570 break;
571
572 case OSM_FLAG_DIRTY:
573 printf(" Valid id (DIRTY)\n");
574
575 if((relation = osm_get_relation_by_id(osm, id)))
576 relation->flags |= OSM_FLAG_DIRTY;
577 else
578 printf(" WARNING: no relation with that id found\n");
579 break;
580
581 default:
582 printf(" Illegal relation entry\n");
583 return;
584 }
585
586 if(!relation) {
587 printf(" no valid relation\n");
588 return;
589 }
590
591 /* update id from diff */
592 relation->id = id;
593
594 /* update members */
595
596 /* this may be an existing relation, so remove members to */
597 /* make space for new ones */
598 if(relation->member) {
599 printf(" removing existing members for diff members\n");
600 osm_members_free(relation->member);
601 relation->member = NULL;
602 }
603
604 /* scan for members */
605 member_t **member = &relation->member;
606 xmlNode *member_node = NULL;
607 for(member_node = node_rel->children; member_node;
608 member_node = member_node->next) {
609 if(member_node->type == XML_ELEMENT_NODE) {
610 if(strcasecmp((char*)member_node->name, "member") == 0) {
611 /* attach member to member_chain */
612 *member = osm_parse_osm_relation_member(osm, doc, member_node);
613 if(*member)
614 member = &((*member)->next);
615 }
616 }
617 }
618
619 /* node may be an existing node, so remove tags to */
620 /* make space for new ones */
621 if(relation->tag) {
622 printf(" removing existing tags for diff tags\n");
623 osm_tag_free(relation->tag);
624 relation->tag = NULL;
625 }
626
627 relation->tag = xml_scan_tags(doc, node_rel->children, osm);
628 }
629
630 void diff_restore(appdata_t *appdata, project_t *project, osm_t *osm) {
631 if(!project || !osm) return;
632
633 char *diff_name = g_strdup_printf("%s/%s.diff", project->path, project->name);
634
635 if(!g_file_test(diff_name, G_FILE_TEST_EXISTS)) {
636 printf("no diff present!\n");
637 g_free(diff_name);
638 return;
639 }
640
641 printf("diff found, applying ...\n");
642
643 xmlDoc *doc = NULL;
644 xmlNode *root_element = NULL;
645
646 /* parse the file and get the DOM */
647 if((doc = xmlReadFile(diff_name, NULL, 0)) == NULL) {
648 errorf(GTK_WIDGET(appdata->window),
649 "Error: could not parse file %s\n", diff_name);
650 g_free(diff_name);
651 return;
652 }
653
654 /* Get the root element node */
655 root_element = xmlDocGetRootElement(doc);
656
657 xmlNode *cur_node = NULL;
658 for (cur_node = root_element; cur_node; cur_node = cur_node->next) {
659 if (cur_node->type == XML_ELEMENT_NODE) {
660 if(strcasecmp((char*)cur_node->name, "diff") == 0) {
661 char *str = (char*)xmlGetProp(cur_node, BAD_CAST "name");
662 if(str) {
663 printf("diff for project %s\n", str);
664 if(strcmp(project->name, str) != 0) {
665 messagef(GTK_WIDGET(appdata->window), _("Warning"),
666 "Diff name (%s) does not match project name (%s)",
667 str, project->name);
668 }
669 xmlFree(str);
670 }
671
672 xmlNodePtr node_node = cur_node->children;
673 while(node_node) {
674 if(node_node->type == XML_ELEMENT_NODE) {
675
676 if(strcasecmp((char*)node_node->name, "node") == 0)
677 diff_restore_node(doc, node_node, osm);
678
679 else if(strcasecmp((char*)node_node->name, "way") == 0)
680 diff_restore_way(doc, node_node, osm);
681
682 else if(strcasecmp((char*)node_node->name, "relation") == 0)
683 diff_restore_relation(doc, node_node, osm);
684
685 else
686 printf("WARNING: item %s not restored\n", node_node->name);
687 }
688 node_node = node_node->next;
689 }
690 }
691 }
692 }
693
694 g_free(diff_name);
695
696 xmlFreeDoc(doc);
697 xmlCleanupParser();
698
699 /* check for hidden ways and update menu accordingly */
700 gboolean something_is_hidden = FALSE;
701 way_t *way = osm->way;
702 while(!something_is_hidden && way) {
703 if(way->flags & OSM_FLAG_HIDDEN)
704 something_is_hidden = TRUE;
705
706 way = way->next;
707 }
708
709 if(something_is_hidden) {
710 printf("hidden flags have been restored, enable show_add menu\n");
711
712 statusbar_set(appdata, _("Some objects have been hidden"), TRUE);
713 gtk_widget_set_sensitive(appdata->menu_item_map_show_all, TRUE);
714 }
715 }
716
717 gboolean diff_present(project_t *project) {
718 char *diff_name = g_strdup_printf("%s/%s.diff", project->path, project->name);
719
720 if(!g_file_test(diff_name, G_FILE_TEST_EXISTS)) {
721 printf("no diff present!\n");
722 g_free(diff_name);
723 return FALSE;
724 }
725
726 g_free(diff_name);
727 return TRUE;
728 }
729
730 void diff_remove(project_t *project) {
731 char *diff_name = g_strdup_printf("%s/%s.diff", project->path, project->name);
732 g_remove(diff_name);
733 g_free(diff_name);
734 }