Parent Directory | Revision Log
Adjusted default path
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 | #include "appdata.h" |
21 | |
22 | #include <curl/curl.h> |
23 | #include <curl/types.h> /* new for v7 */ |
24 | #include <curl/easy.h> /* new for v7 */ |
25 | #include <unistd.h> |
26 | |
27 | #define COLOR_ERR "red" |
28 | #define COLOR_OK "darkgreen" |
29 | |
30 | #define NO_EXPECT |
31 | |
32 | static struct http_message_s { |
33 | int id; |
34 | char *msg; |
35 | } http_messages [] = { |
36 | { 200, "Ok" }, |
37 | { 203, "No Content" }, |
38 | { 301, "Moved Permenently" }, |
39 | { 302, "Moved Temporarily" }, |
40 | { 400, "Bad Request" }, |
41 | { 401, "Unauthorized" }, |
42 | { 403, "Forbidden" }, |
43 | { 404, "Not Found" }, |
44 | { 405, "Method Not Allowed" }, |
45 | { 409, "Conflict" }, |
46 | { 410, "Gone" }, |
47 | { 412, "Precondition Failed" }, |
48 | { 417, "(Expect rejected)" }, |
49 | { 500, "Internal Server Error" }, |
50 | { 503, "Service Unavailable" }, |
51 | { 0, NULL } |
52 | }; |
53 | |
54 | static char *osm_http_message(int id) { |
55 | struct http_message_s *msg = http_messages; |
56 | |
57 | while(msg->id) { |
58 | if(msg->id == id) return _(msg->msg); |
59 | msg++; |
60 | } |
61 | |
62 | return NULL; |
63 | } |
64 | |
65 | typedef struct { |
66 | appdata_t *appdata; |
67 | GtkWidget *dialog; |
68 | osm_t *osm; |
69 | project_t *project; |
70 | |
71 | struct log_s { |
72 | GtkTextBuffer *buffer; |
73 | GtkWidget *view; |
74 | } log; |
75 | |
76 | item_id_t changeset; |
77 | char *comment; |
78 | |
79 | proxy_t *proxy; |
80 | } osm_upload_context_t; |
81 | |
82 | gboolean osm_download(GtkWidget *parent, settings_t *settings, |
83 | project_t *project) { |
84 | printf("download osm ...\n"); |
85 | |
86 | g_assert(project->server); |
87 | |
88 | /* check if server name contains string "0.5" and adjust it */ |
89 | if(strstr(project->server, "0.5") != NULL) { |
90 | strstr(project->server, "0.5")[2] = '6'; |
91 | |
92 | messagef(parent, _("Server changed"), |
93 | _("It seems your current project uses a server/protocol no " |
94 | "longer in use by OSM. It has thus been changed to:\n\n%s"), |
95 | project->server); |
96 | project->dirty = TRUE; // project needs to be changed |
97 | } else |
98 | printf("url ok\n"); |
99 | |
100 | char minlon[G_ASCII_DTOSTR_BUF_SIZE], minlat[G_ASCII_DTOSTR_BUF_SIZE]; |
101 | char maxlon[G_ASCII_DTOSTR_BUF_SIZE], maxlat[G_ASCII_DTOSTR_BUF_SIZE]; |
102 | |
103 | g_ascii_formatd(minlon, sizeof(minlon), LL_FORMAT, project->min.lon); |
104 | g_ascii_formatd(minlat, sizeof(minlat), LL_FORMAT, project->min.lat); |
105 | g_ascii_formatd(maxlon, sizeof(maxlon), LL_FORMAT, project->max.lon); |
106 | g_ascii_formatd(maxlat, sizeof(maxlat), LL_FORMAT, project->max.lat); |
107 | |
108 | /* server url should not end with a slash */ |
109 | if(project->server[strlen(project->server)-1] == '/') { |
110 | printf("removing trailing slash\n"); |
111 | project->server[strlen(project->server)-1] = 0; |
112 | project->dirty = TRUE; |
113 | } |
114 | |
115 | char *url = g_strdup_printf("%s/map?bbox=%s,%s,%s,%s", |
116 | project->server, minlon, minlat, maxlon, maxlat); |
117 | |
118 | char *str = NULL; |
119 | if(project->osm[0] == '/') str = g_strdup(project->osm); |
120 | else str = g_strjoin(NULL, project->path, project->osm, NULL); |
121 | gboolean result = net_io_download_file(parent, settings, url, str); |
122 | g_free(str); |
123 | |
124 | g_free(url); |
125 | return result; |
126 | } |
127 | |
128 | typedef struct { |
129 | char *ptr; |
130 | int len; |
131 | } curl_data_t; |
132 | |
133 | static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *stream) { |
134 | curl_data_t *p = (curl_data_t*)stream; |
135 | |
136 | // printf("request to read %d items of size %d, pointer = %p\n", |
137 | // nmemb, size, p->ptr); |
138 | |
139 | if(nmemb*size > p->len) |
140 | nmemb = p->len/size; |
141 | |
142 | memcpy(ptr, p->ptr, size*nmemb); |
143 | p->ptr += size*nmemb; |
144 | p->len -= size*nmemb; |
145 | |
146 | return nmemb; |
147 | } |
148 | |
149 | static size_t write_callback(void *ptr, size_t size, size_t nmemb, void *stream) { |
150 | curl_data_t *p = (curl_data_t*)stream; |
151 | |
152 | p->ptr = g_realloc(p->ptr, p->len + size*nmemb + 1); |
153 | if(p->ptr) { |
154 | memcpy(p->ptr+p->len, ptr, size*nmemb); |
155 | p->len += size*nmemb; |
156 | p->ptr[p->len] = 0; |
157 | } |
158 | return nmemb; |
159 | } |
160 | |
161 | static void appendf(struct log_s *log, char *colname, |
162 | const char *fmt, ...) { |
163 | va_list args; |
164 | va_start( args, fmt ); |
165 | char *buf = g_strdup_vprintf(fmt, args); |
166 | va_end( args ); |
167 | |
168 | printf(buf); |
169 | |
170 | GtkTextTag *tag = NULL; |
171 | if(colname) |
172 | tag = gtk_text_buffer_create_tag(log->buffer, NULL, |
173 | "foreground", colname, |
174 | NULL); |
175 | |
176 | GtkTextIter end; |
177 | gtk_text_buffer_get_end_iter(log->buffer, &end); |
178 | if(tag) |
179 | gtk_text_buffer_insert_with_tags(log->buffer, &end, buf, -1, tag, NULL); |
180 | else |
181 | gtk_text_buffer_insert(log->buffer, &end, buf, -1); |
182 | |
183 | g_free(buf); |
184 | |
185 | gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(log->view), |
186 | &end, 0.0, FALSE, 0, 0); |
187 | |
188 | while(gtk_events_pending()) |
189 | gtk_main_iteration(); |
190 | } |
191 | |
192 | #define MAX_TRY 5 |
193 | |
194 | static gboolean osm_update_item(struct log_s *log, char *xml_str, |
195 | char *url, char *user, item_id_t *id, |
196 | proxy_t *proxy) { |
197 | int retry = MAX_TRY; |
198 | char buffer[CURL_ERROR_SIZE]; |
199 | |
200 | CURL *curl; |
201 | CURLcode res; |
202 | |
203 | curl_data_t read_data; |
204 | curl_data_t write_data; |
205 | |
206 | while(retry >= 0) { |
207 | |
208 | if(retry != MAX_TRY) |
209 | appendf(log, NULL, _("Retry %d/%d "), MAX_TRY-retry, MAX_TRY-1); |
210 | |
211 | /* get a curl handle */ |
212 | curl = curl_easy_init(); |
213 | if(!curl) { |
214 | appendf(log, NULL, _("CURL init error\n")); |
215 | return FALSE; |
216 | } |
217 | |
218 | read_data.ptr = xml_str; |
219 | read_data.len = xml_str?strlen(xml_str):0; |
220 | write_data.ptr = NULL; |
221 | write_data.len = 0; |
222 | |
223 | /* we want to use our own read/write functions */ |
224 | curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback); |
225 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); |
226 | |
227 | /* enable uploading */ |
228 | curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); |
229 | |
230 | /* specify target URL, and note that this URL should include a file |
231 | name, not only a directory */ |
232 | curl_easy_setopt(curl, CURLOPT_URL, url); |
233 | |
234 | /* now specify which file to upload */ |
235 | curl_easy_setopt(curl, CURLOPT_READDATA, &read_data); |
236 | |
237 | /* provide the size of the upload, we specicially typecast the value |
238 | to curl_off_t since we must be sure to use the correct data size */ |
239 | curl_easy_setopt(curl, CURLOPT_INFILESIZE, (curl_off_t)read_data.len); |
240 | |
241 | /* we pass our 'chunk' struct to the callback function */ |
242 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &write_data); |
243 | |
244 | /* some servers don't like requests that are made without a user-agent |
245 | field, so we provide one */ |
246 | curl_easy_setopt(curl, CURLOPT_USERAGENT, PACKAGE "-libcurl/" VERSION); |
247 | |
248 | #ifdef NO_EXPECT |
249 | struct curl_slist *slist = NULL; |
250 | slist = curl_slist_append(slist, "Expect:"); |
251 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); |
252 | #endif |
253 | |
254 | curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, buffer); |
255 | |
256 | /* set user name and password for the authentication */ |
257 | curl_easy_setopt(curl, CURLOPT_USERPWD, user); |
258 | |
259 | net_io_set_proxy(curl, proxy); |
260 | |
261 | /* Now run off and do what you've been told! */ |
262 | res = curl_easy_perform(curl); |
263 | |
264 | long response; |
265 | curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response); |
266 | |
267 | /* always cleanup */ |
268 | #ifdef NO_EXPECT |
269 | curl_slist_free_all(slist); |
270 | #endif |
271 | curl_easy_cleanup(curl); |
272 | |
273 | /* this will return the id on a successful create */ |
274 | if(id && (res == 0) && (response == 200)) { |
275 | printf("request to parse successful reply as an id\n"); |
276 | *id = strtoul(write_data.ptr, NULL, 10); |
277 | } |
278 | |
279 | if(res != 0) |
280 | appendf(log, COLOR_ERR, _("failed: %s\n"), buffer); |
281 | else if(response != 200) |
282 | appendf(log, COLOR_ERR, _("failed, code: %ld %s\n"), |
283 | response, osm_http_message(response)); |
284 | else { |
285 | if(!id) appendf(log, COLOR_OK, _("ok\n")); |
286 | else appendf(log, COLOR_OK, _("ok: #%ld\n"), *id); |
287 | } |
288 | |
289 | /* if it's neither "ok" (200), nor "internal server error" (500) */ |
290 | /* then write the message to the log */ |
291 | if((response != 200) && (response != 500) && write_data.ptr) { |
292 | appendf(log, NULL, _("Server reply: ")); |
293 | appendf(log, COLOR_ERR, _("%s\n"), write_data.ptr); |
294 | } |
295 | |
296 | if(write_data.ptr) |
297 | g_free(write_data.ptr); |
298 | |
299 | /* don't retry unless we had an "internal server error" */ |
300 | if(response != 500) |
301 | return((res == 0)&&(response == 200)); |
302 | |
303 | retry--; |
304 | } |
305 | |
306 | return FALSE; |
307 | } |
308 | |
309 | static gboolean osm_delete_item(struct log_s *log, char *xml_str, |
310 | char *url, char *user, proxy_t *proxy) { |
311 | int retry = MAX_TRY; |
312 | char buffer[CURL_ERROR_SIZE]; |
313 | |
314 | CURL *curl; |
315 | CURLcode res; |
316 | |
317 | /* delete has a payload since api 0.6 */ |
318 | curl_data_t read_data; |
319 | curl_data_t write_data; |
320 | |
321 | while(retry >= 0) { |
322 | |
323 | if(retry != MAX_TRY) |
324 | appendf(log, NULL, _("Retry %d/%d "), MAX_TRY-retry, MAX_TRY-1); |
325 | |
326 | /* get a curl handle */ |
327 | curl = curl_easy_init(); |
328 | if(!curl) { |
329 | appendf(log, NULL, _("CURL init error\n")); |
330 | return FALSE; |
331 | } |
332 | |
333 | read_data.ptr = xml_str; |
334 | read_data.len = xml_str?strlen(xml_str):0; |
335 | write_data.ptr = NULL; |
336 | write_data.len = 0; |
337 | |
338 | /* we want to use our own read/write functions */ |
339 | curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback); |
340 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); |
341 | |
342 | curl_easy_setopt(curl, CURLOPT_INFILESIZE, (curl_off_t)read_data.len); |
343 | |
344 | /* now specify which file to upload */ |
345 | curl_easy_setopt(curl, CURLOPT_READDATA, &read_data); |
346 | |
347 | /* we pass our 'chunk' struct to the callback function */ |
348 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &write_data); |
349 | |
350 | /* enable uploading */ |
351 | curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); |
352 | |
353 | /* no read/write functions required */ |
354 | curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); |
355 | |
356 | /* specify target URL, and note that this URL should include a file |
357 | name, not only a directory */ |
358 | curl_easy_setopt(curl, CURLOPT_URL, url); |
359 | |
360 | /* some servers don't like requests that are made without a user-agent |
361 | field, so we provide one */ |
362 | curl_easy_setopt(curl, CURLOPT_USERAGENT, PACKAGE "-libcurl/" VERSION); |
363 | |
364 | #ifdef NO_EXPECT |
365 | struct curl_slist *slist = NULL; |
366 | slist = curl_slist_append(slist, "Expect:"); |
367 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); |
368 | #endif |
369 | |
370 | curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, buffer); |
371 | |
372 | /* set user name and password for the authentication */ |
373 | curl_easy_setopt(curl, CURLOPT_USERPWD, user); |
374 | |
375 | net_io_set_proxy(curl, proxy); |
376 | |
377 | /* Now run off and do what you've been told! */ |
378 | res = curl_easy_perform(curl); |
379 | |
380 | long response; |
381 | curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response); |
382 | |
383 | /* always cleanup */ |
384 | #ifdef NO_EXPECT |
385 | curl_slist_free_all(slist); |
386 | #endif |
387 | curl_easy_cleanup(curl); |
388 | |
389 | if(res != 0) |
390 | appendf(log, COLOR_ERR, _("failed: %s\n"), buffer); |
391 | else if(response != 200) |
392 | appendf(log, COLOR_ERR, _("failed, code: %ld %s\n"), |
393 | response, osm_http_message(response)); |
394 | else |
395 | appendf(log, COLOR_OK, _("ok\n")); |
396 | |
397 | /* if it's neither "ok" (200), nor "internal server error" (500) */ |
398 | /* then write the message to the log */ |
399 | if((response != 200) && (response != 500) && write_data.ptr) { |
400 | appendf(log, NULL, _("Server reply: ")); |
401 | appendf(log, COLOR_ERR, _("%s\n"), write_data.ptr); |
402 | } |
403 | |
404 | if(write_data.ptr) |
405 | g_free(write_data.ptr); |
406 | |
407 | /* don't retry unless we had an "internal server error" */ |
408 | if(response != 500) |
409 | return((res == 0)&&(response == 200)); |
410 | |
411 | retry--; |
412 | } |
413 | |
414 | return FALSE; |
415 | } |
416 | |
417 | typedef struct { |
418 | struct { |
419 | int total, new, dirty, deleted; |
420 | } ways, nodes, relations; |
421 | } osm_dirty_t; |
422 | |
423 | static GtkWidget *table_attach_label_c(GtkWidget *table, char *str, |
424 | int x1, int x2, int y1, int y2) { |
425 | GtkWidget *label = gtk_label_new(str); |
426 | gtk_table_attach_defaults(GTK_TABLE(table), label, x1, x2, y1, y2); |
427 | return label; |
428 | } |
429 | |
430 | static GtkWidget *table_attach_label_l(GtkWidget *table, char *str, |
431 | int x1, int x2, int y1, int y2) { |
432 | GtkWidget *label = table_attach_label_c(table, str, x1, x2, y1, y2); |
433 | gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f); |
434 | return label; |
435 | } |
436 | |
437 | static GtkWidget *table_attach_int(GtkWidget *table, int num, |
438 | int x1, int x2, int y1, int y2) { |
439 | char *str = g_strdup_printf("%d", num); |
440 | GtkWidget *label = table_attach_label_c(table, str, x1, x2, y1, y2); |
441 | g_free(str); |
442 | return label; |
443 | } |
444 | |
445 | static void osm_delete_nodes(osm_upload_context_t *context) { |
446 | node_t *node = context->osm->node; |
447 | project_t *project = context->project; |
448 | |
449 | while(node) { |
450 | /* make sure gui gets updated */ |
451 | while(gtk_events_pending()) gtk_main_iteration(); |
452 | |
453 | if(node->flags & OSM_FLAG_DELETED) { |
454 | printf("deleting node on server\n"); |
455 | |
456 | appendf(&context->log, NULL, _("Delete node #%ld "), node->id); |
457 | |
458 | char *url = g_strdup_printf("%s/node/" ITEM_ID_FORMAT, |
459 | project->server, node->id); |
460 | char *cred = g_strdup_printf("%s:%s", |
461 | context->appdata->settings->username, |
462 | context->appdata->settings->password); |
463 | |
464 | char *xml_str = |
465 | osm_generate_xml_node(context->osm, context->changeset, node); |
466 | |
467 | if(osm_delete_item(&context->log, xml_str, url, cred, context->proxy)) { |
468 | node->flags &= ~(OSM_FLAG_DIRTY | OSM_FLAG_DELETED); |
469 | project->data_dirty = TRUE; |
470 | } |
471 | |
472 | g_free(cred); |
473 | } |
474 | node = node->next; |
475 | } |
476 | } |
477 | |
478 | static void osm_upload_nodes(osm_upload_context_t *context) { |
479 | node_t *node = context->osm->node; |
480 | project_t *project = context->project; |
481 | |
482 | while(node) { |
483 | /* make sure gui gets updated */ |
484 | while(gtk_events_pending()) gtk_main_iteration(); |
485 | |
486 | if((node->flags & (OSM_FLAG_DIRTY | OSM_FLAG_NEW)) && |
487 | (!(node->flags & OSM_FLAG_DELETED))) { |
488 | char *url = NULL; |
489 | |
490 | if(node->flags & OSM_FLAG_NEW) { |
491 | url = g_strdup_printf("%s/node/create", project->server); |
492 | appendf(&context->log, NULL, _("New node ")); |
493 | } else { |
494 | url = g_strdup_printf("%s/node/" ITEM_ID_FORMAT, |
495 | project->server, node->id); |
496 | appendf(&context->log, NULL, _("Modified node #%ld "), node->id); |
497 | } |
498 | |
499 | /* upload this node */ |
500 | char *xml_str = |
501 | osm_generate_xml_node(context->osm, context->changeset, node); |
502 | if(xml_str) { |
503 | printf("uploading node %s from address %p\n", url, xml_str); |
504 | |
505 | char *cred = g_strdup_printf("%s:%s", |
506 | context->appdata->settings->username, |
507 | context->appdata->settings->password); |
508 | if(osm_update_item(&context->log, xml_str, url, cred, |
509 | (node->flags & OSM_FLAG_NEW)?&(node->id):&(node->version), |
510 | context->proxy)) { |
511 | node->flags &= ~(OSM_FLAG_DIRTY | OSM_FLAG_NEW); |
512 | project->data_dirty = TRUE; |
513 | } |
514 | g_free(cred); |
515 | } |
516 | g_free(url); |
517 | } |
518 | node = node->next; |
519 | } |
520 | } |
521 | |
522 | static void osm_delete_ways(osm_upload_context_t *context) { |
523 | way_t *way = context->osm->way; |
524 | project_t *project = context->project; |
525 | |
526 | while(way) { |
527 | /* make sure gui gets updated */ |
528 | while(gtk_events_pending()) gtk_main_iteration(); |
529 | |
530 | if(way->flags & OSM_FLAG_DELETED) { |
531 | printf("deleting way on server\n"); |
532 | |
533 | appendf(&context->log, NULL, _("Delete way #%ld "), way->id); |
534 | |
535 | char *url = g_strdup_printf("%s/way/" ITEM_ID_FORMAT, |
536 | project->server, way->id); |
537 | char *cred = g_strdup_printf("%s:%s", |
538 | context->appdata->settings->username, |
539 | context->appdata->settings->password); |
540 | |
541 | char *xml_str = |
542 | osm_generate_xml_way(context->osm, context->changeset, way); |
543 | |
544 | if(osm_delete_item(&context->log, xml_str, url, cred, context->proxy)) { |
545 | way->flags &= ~(OSM_FLAG_DIRTY | OSM_FLAG_DELETED); |
546 | project->data_dirty = TRUE; |
547 | } |
548 | |
549 | g_free(cred); |
550 | } |
551 | way = way->next; |
552 | } |
553 | } |
554 | |
555 | |
556 | static void osm_upload_ways(osm_upload_context_t *context) { |
557 | way_t *way = context->osm->way; |
558 | project_t *project = context->project; |
559 | |
560 | while(way) { |
561 | /* make sure gui gets updated */ |
562 | while(gtk_events_pending()) gtk_main_iteration(); |
563 | |
564 | if((way->flags & (OSM_FLAG_DIRTY | OSM_FLAG_NEW)) && |
565 | (!(way->flags & OSM_FLAG_DELETED))) { |
566 | char *url = NULL; |
567 | |
568 | if(way->flags & OSM_FLAG_NEW) { |
569 | url = g_strdup_printf("%s/way/create", project->server); |
570 | appendf(&context->log, NULL, _("New way ")); |
571 | } else { |
572 | url = g_strdup_printf("%s/way/" ITEM_ID_FORMAT, |
573 | project->server, way->id); |
574 | appendf(&context->log, NULL, _("Modified way #%ld "), way->id); |
575 | } |
576 | |
577 | /* upload this node */ |
578 | char *xml_str = |
579 | osm_generate_xml_way(context->osm, context->changeset, way); |
580 | if(xml_str) { |
581 | printf("uploading way %s from address %p\n", url, xml_str); |
582 | |
583 | char *cred = g_strdup_printf("%s:%s", |
584 | context->appdata->settings->username, |
585 | context->appdata->settings->password); |
586 | if(osm_update_item(&context->log, xml_str, url, cred, |
587 | (way->flags & OSM_FLAG_NEW)?&(way->id):&(way->version), |
588 | context->proxy)) { |
589 | way->flags &= ~(OSM_FLAG_DIRTY | OSM_FLAG_NEW); |
590 | project->data_dirty = TRUE; |
591 | } |
592 | g_free(cred); |
593 | } |
594 | g_free(url); |
595 | } |
596 | way = way->next; |
597 | } |
598 | } |
599 | |
600 | static void osm_delete_relations(osm_upload_context_t *context) { |
601 | relation_t *relation = context->osm->relation; |
602 | project_t *project = context->project; |
603 | |
604 | while(relation) { |
605 | /* make sure gui gets updated */ |
606 | while(gtk_events_pending()) gtk_main_iteration(); |
607 | |
608 | if(relation->flags & OSM_FLAG_DELETED) { |
609 | printf("deleting relation on server\n"); |
610 | |
611 | appendf(&context->log, NULL, _("Delete relation #%ld "), relation->id); |
612 | |
613 | char *url = g_strdup_printf("%s/relation/" ITEM_ID_FORMAT, |
614 | project->server, relation->id); |
615 | char *cred = g_strdup_printf("%s:%s", |
616 | context->appdata->settings->username, |
617 | context->appdata->settings->password); |
618 | |
619 | char *xml_str = |
620 | osm_generate_xml_relation(context->osm, context->changeset, relation); |
621 | |
622 | if(osm_delete_item(&context->log, xml_str, url, cred, context->proxy)) { |
623 | relation->flags &= ~(OSM_FLAG_DIRTY | OSM_FLAG_DELETED); |
624 | project->data_dirty = TRUE; |
625 | } |
626 | |
627 | g_free(cred); |
628 | } |
629 | relation = relation->next; |
630 | } |
631 | } |
632 | |
633 | |
634 | static void osm_upload_relations(osm_upload_context_t *context) { |
635 | relation_t *relation = context->osm->relation; |
636 | project_t *project = context->project; |
637 | |
638 | while(relation) { |
639 | /* make sure gui gets updated */ |
640 | while(gtk_events_pending()) gtk_main_iteration(); |
641 | |
642 | if((relation->flags & (OSM_FLAG_DIRTY | OSM_FLAG_NEW)) && |
643 | (!(relation->flags & OSM_FLAG_DELETED))) { |
644 | char *url = NULL; |
645 | |
646 | if(relation->flags & OSM_FLAG_NEW) { |
647 | url = g_strdup_printf("%s/relation/create", project->server); |
648 | appendf(&context->log, NULL, _("New relation ")); |
649 | } else { |
650 | url = g_strdup_printf("%s/relation/" ITEM_ID_FORMAT, |
651 | project->server,relation->id); |
652 | appendf(&context->log, NULL, _("Modified relation #%ld "), |
653 | relation->id); |
654 | } |
655 | |
656 | /* upload this relation */ |
657 | char *xml_str = |
658 | osm_generate_xml_relation(context->osm, context->changeset, relation); |
659 | if(xml_str) { |
660 | printf("uploading relation %s from address %p\n", url, xml_str); |
661 | |
662 | char *cred = g_strdup_printf("%s:%s", |
663 | context->appdata->settings->username, |
664 | context->appdata->settings->password); |
665 | if(osm_update_item(&context->log, xml_str, url, cred, |
666 | (relation->flags & OSM_FLAG_NEW)?&(relation->id):&(relation->version), |
667 | context->proxy)) { |
668 | relation->flags &= ~(OSM_FLAG_DIRTY | OSM_FLAG_NEW); |
669 | project->data_dirty = TRUE; |
670 | } |
671 | g_free(cred); |
672 | } |
673 | g_free(url); |
674 | } |
675 | relation = relation->next; |
676 | } |
677 | } |
678 | |
679 | static gboolean osm_create_changeset(osm_upload_context_t *context) { |
680 | gboolean result = FALSE; |
681 | context->changeset = ILLEGAL; |
682 | project_t *project = context->project; |
683 | |
684 | /* make sure gui gets updated */ |
685 | while(gtk_events_pending()) gtk_main_iteration(); |
686 | |
687 | char *url = g_strdup_printf("%s/changeset/create", project->server); |
688 | appendf(&context->log, NULL, _("Create changeset ")); |
689 | |
690 | /* create changeset request */ |
691 | char *xml_str = osm_generate_xml_changeset(context->osm, context->comment); |
692 | if(xml_str) { |
693 | printf("creating changeset %s from address %p\n", url, xml_str); |
694 | |
695 | char *cred = g_strdup_printf("%s:%s", |
696 | context->appdata->settings->username, |
697 | context->appdata->settings->password); |
698 | |
699 | if(osm_update_item(&context->log, xml_str, url, cred, |
700 | &context->changeset, context->proxy)) { |
701 | printf("got changeset id " ITEM_ID_FORMAT "\n", context->changeset); |
702 | result = TRUE; |
703 | } |
704 | |
705 | g_free(cred); |
706 | } |
707 | g_free(url); |
708 | |
709 | return result; |
710 | } |
711 | |
712 | static gboolean osm_close_changeset(osm_upload_context_t *context) { |
713 | gboolean result = FALSE; |
714 | project_t *project = context->project; |
715 | |
716 | g_assert(context->changeset != ILLEGAL); |
717 | |
718 | /* make sure gui gets updated */ |
719 | while(gtk_events_pending()) gtk_main_iteration(); |
720 | |
721 | char *url = g_strdup_printf("%s/changeset/" ITEM_ID_FORMAT "/close", |
722 | project->server, context->changeset); |
723 | appendf(&context->log, NULL, _("Close changeset ")); |
724 | |
725 | char *cred = g_strdup_printf("%s:%s", |
726 | context->appdata->settings->username, |
727 | context->appdata->settings->password); |
728 | |
729 | if(osm_update_item(&context->log, NULL, url, cred, NULL, context->proxy)) |
730 | result = TRUE; |
731 | |
732 | g_free(cred); |
733 | g_free(url); |
734 | |
735 | return result; |
736 | } |
737 | |
738 | /* comment buffer has been edited, allow upload if the buffer is not empty */ |
739 | static void callback_buffer_modified(GtkTextBuffer *buffer, GtkDialog *dialog) { |
740 | GtkTextIter start, end; |
741 | gtk_text_buffer_get_start_iter(buffer, &start); |
742 | gtk_text_buffer_get_end_iter(buffer, &end); |
743 | char *text = gtk_text_buffer_get_text(buffer, &start, &end, FALSE); |
744 | gtk_dialog_set_response_sensitive(dialog, GTK_RESPONSE_ACCEPT, |
745 | text && strlen(text)); |
746 | } |
747 | |
748 | static gboolean cb_focus_in(GtkTextView *view, GdkEventFocus *event, |
749 | GtkTextBuffer *buffer) { |
750 | |
751 | gboolean first_click = |
752 | GPOINTER_TO_INT(g_object_get_data(G_OBJECT(view), "first_click")); |
753 | |
754 | g_object_set_data(G_OBJECT(view), "first_click", GINT_TO_POINTER(FALSE)); |
755 | |
756 | if(first_click) { |
757 | GtkTextIter start, end; |
758 | gtk_text_buffer_get_start_iter(buffer, &start); |
759 | gtk_text_buffer_get_end_iter(buffer, &end); |
760 | gtk_text_buffer_delete(buffer, &start, &end); |
761 | } |
762 | |
763 | return FALSE; |
764 | } |
765 | |
766 | void osm_upload(appdata_t *appdata, osm_t *osm, project_t *project) { |
767 | |
768 | printf("starting upload\n"); |
769 | |
770 | /* upload config and confirmation dialog */ |
771 | |
772 | /* count nodes */ |
773 | osm_dirty_t dirty; |
774 | memset(&dirty, 0, sizeof(osm_dirty_t)); |
775 | |
776 | node_t *node = osm->node; |
777 | while(node) { |
778 | dirty.nodes.total++; |
779 | if(node->flags & OSM_FLAG_DELETED) dirty.nodes.deleted++; |
780 | else if(node->flags & OSM_FLAG_NEW) dirty.nodes.new++; |
781 | else if(node->flags & OSM_FLAG_DIRTY) dirty.nodes.dirty++; |
782 | |
783 | node = node->next; |
784 | } |
785 | printf("nodes: new %2d, dirty %2d, deleted %2d\n", |
786 | dirty.nodes.new, dirty.nodes.dirty, dirty.nodes.deleted); |
787 | |
788 | /* count ways */ |
789 | way_t *way = osm->way; |
790 | while(way) { |
791 | dirty.ways.total++; |
792 | if(way->flags & OSM_FLAG_DELETED) dirty.ways.deleted++; |
793 | else if(way->flags & OSM_FLAG_NEW) dirty.ways.new++; |
794 | else if(way->flags & OSM_FLAG_DIRTY) dirty.ways.dirty++; |
795 | |
796 | way = way->next; |
797 | } |
798 | printf("ways: new %2d, dirty %2d, deleted %2d\n", |
799 | dirty.ways.new, dirty.ways.dirty, dirty.ways.deleted); |
800 | |
801 | /* count relations */ |
802 | relation_t *relation = osm->relation; |
803 | while(relation) { |
804 | dirty.relations.total++; |
805 | if(relation->flags & OSM_FLAG_DELETED) dirty.relations.deleted++; |
806 | else if(relation->flags & OSM_FLAG_NEW) dirty.relations.new++; |
807 | else if(relation->flags & OSM_FLAG_DIRTY) dirty.relations.dirty++; |
808 | |
809 | relation = relation->next; |
810 | } |
811 | printf("relations: new %2d, dirty %2d, deleted %2d\n", |
812 | dirty.relations.new, dirty.relations.dirty, dirty.relations.deleted); |
813 | |
814 | |
815 | GtkWidget *dialog = |
816 | misc_dialog_new(MISC_DIALOG_NOSIZE, _("Upload to OSM"), |
817 | GTK_WINDOW(appdata->window), |
818 | GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, |
819 | GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, |
820 | NULL); |
821 | |
822 | GtkWidget *table = gtk_table_new(4, 5, TRUE); |
823 | |
824 | table_attach_label_c(table, _("Total"), 1, 2, 0, 1); |
825 | table_attach_label_c(table, _("New"), 2, 3, 0, 1); |
826 | table_attach_label_c(table, _("Modified"), 3, 4, 0, 1); |
827 | table_attach_label_c(table, _("Deleted"), 4, 5, 0, 1); |
828 | |
829 | table_attach_label_l(table, _("Nodes:"), 0, 1, 1, 2); |
830 | table_attach_int(table, dirty.nodes.total, 1, 2, 1, 2); |
831 | table_attach_int(table, dirty.nodes.new, 2, 3, 1, 2); |
832 | table_attach_int(table, dirty.nodes.dirty, 3, 4, 1, 2); |
833 | table_attach_int(table, dirty.nodes.deleted, 4, 5, 1, 2); |
834 | |
835 | table_attach_label_l(table, _("Ways:"), 0, 1, 2, 3); |
836 | table_attach_int(table, dirty.ways.total, 1, 2, 2, 3); |
837 | table_attach_int(table, dirty.ways.new, 2, 3, 2, 3); |
838 | table_attach_int(table, dirty.ways.dirty, 3, 4, 2, 3); |
839 | table_attach_int(table, dirty.ways.deleted, 4, 5, 2, 3); |
840 | |
841 | table_attach_label_l(table, _("Relations:"), 0, 1, 3, 4); |
842 | table_attach_int(table, dirty.relations.total, 1, 2, 3, 4); |
843 | table_attach_int(table, dirty.relations.new, 2, 3, 3, 4); |
844 | table_attach_int(table, dirty.relations.dirty, 3, 4, 3, 4); |
845 | table_attach_int(table, dirty.relations.deleted, 4, 5, 3, 4); |
846 | |
847 | gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), table); |
848 | |
849 | /* ------------------------------------------------------ */ |
850 | |
851 | gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), |
852 | gtk_hseparator_new()); |
853 | |
854 | /* ------- add username and password entries ------------ */ |
855 | |
856 | table = gtk_table_new(2, 2, FALSE); |
857 | table_attach_label_l(table, _("Username:"), 0, 1, 0, 1); |
858 | GtkWidget *uentry = gtk_entry_new(); |
859 | HILDON_ENTRY_NO_AUTOCAP(uentry); |
860 | gtk_entry_set_text(GTK_ENTRY(uentry), appdata->settings->username); |
861 | gtk_table_attach_defaults(GTK_TABLE(table), uentry, 1, 2, 0, 1); |
862 | table_attach_label_l(table, _("Password:"), 0, 1, 1, 2); |
863 | GtkWidget *pentry = gtk_entry_new(); |
864 | HILDON_ENTRY_NO_AUTOCAP(pentry); |
865 | gtk_entry_set_text(GTK_ENTRY(pentry), appdata->settings->password); |
866 | gtk_entry_set_visibility(GTK_ENTRY(pentry), FALSE); |
867 | gtk_table_attach_defaults(GTK_TABLE(table), pentry, 1, 2, 1, 2); |
868 | gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), table); |
869 | |
870 | GtkWidget *scrolled_win = gtk_scrolled_window_new(NULL, NULL); |
871 | gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_win), |
872 | GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); |
873 | gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_win), |
874 | GTK_SHADOW_IN); |
875 | |
876 | GtkTextBuffer *buffer = gtk_text_buffer_new(NULL); |
877 | gtk_text_buffer_set_text(buffer, _("Please add a comment"), -1); |
878 | |
879 | /* disable ok button until user edited the comment */ |
880 | gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), |
881 | GTK_RESPONSE_ACCEPT, FALSE); |
882 | |
883 | g_signal_connect(G_OBJECT(buffer), "changed", |
884 | G_CALLBACK(callback_buffer_modified), dialog); |
885 | |
886 | GtkWidget *view = gtk_text_view_new_with_buffer(buffer); |
887 | gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD); |
888 | gtk_text_view_set_editable(GTK_TEXT_VIEW(view), TRUE); |
889 | gtk_text_view_set_left_margin(GTK_TEXT_VIEW(view), 2 ); |
890 | gtk_text_view_set_right_margin(GTK_TEXT_VIEW(view), 2 ); |
891 | |
892 | g_object_set_data(G_OBJECT(view), "first_click", GINT_TO_POINTER(TRUE)); |
893 | g_signal_connect(G_OBJECT(view), "focus-in-event", |
894 | G_CALLBACK(cb_focus_in), buffer); |
895 | |
896 | |
897 | gtk_container_add(GTK_CONTAINER(scrolled_win), view); |
898 | |
899 | gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), |
900 | scrolled_win); |
901 | gtk_widget_show_all(dialog); |
902 | |
903 | if(GTK_RESPONSE_ACCEPT != gtk_dialog_run(GTK_DIALOG(dialog))) { |
904 | printf("upload cancelled\n"); |
905 | gtk_widget_destroy(dialog); |
906 | return; |
907 | } |
908 | |
909 | printf("clicked ok\n"); |
910 | |
911 | /* retrieve username and password */ |
912 | if(appdata->settings->username) |
913 | g_free(appdata->settings->username); |
914 | appdata->settings->username = |
915 | g_strdup(gtk_entry_get_text(GTK_ENTRY(uentry))); |
916 | |
917 | if(appdata->settings->password) |
918 | g_free(appdata->settings->password); |
919 | appdata->settings->password = |
920 | g_strdup(gtk_entry_get_text(GTK_ENTRY(pentry))); |
921 | |
922 | /* osm upload itself also has a gui */ |
923 | osm_upload_context_t *context = g_new0(osm_upload_context_t, 1); |
924 | context->appdata = appdata; |
925 | context->osm = osm; |
926 | context->project = project; |
927 | |
928 | /* add proxy settings if required */ |
929 | if(appdata->settings) |
930 | context->proxy = appdata->settings->proxy; |
931 | |
932 | /* fetch comment from dialog */ |
933 | GtkTextIter start, end; |
934 | gtk_text_buffer_get_start_iter(buffer, &start); |
935 | gtk_text_buffer_get_end_iter(buffer, &end); |
936 | char *text = gtk_text_buffer_get_text(buffer, &start, &end, FALSE); |
937 | if(text) context->comment = g_strdup(text); |
938 | |
939 | gtk_widget_destroy(dialog); |
940 | project_save(GTK_WIDGET(appdata->window), project); |
941 | |
942 | context->dialog = |
943 | misc_dialog_new(MISC_DIALOG_LARGE,_("Uploading..."), |
944 | GTK_WINDOW(appdata->window), |
945 | GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL); |
946 | |
947 | gtk_dialog_set_response_sensitive(GTK_DIALOG(context->dialog), |
948 | GTK_RESPONSE_CLOSE, FALSE); |
949 | |
950 | /* ------- main ui elelent is this text view --------------- */ |
951 | |
952 | GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL); |
953 | gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), |
954 | GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); |
955 | |
956 | context->log.buffer = gtk_text_buffer_new(NULL); |
957 | |
958 | context->log.view = gtk_text_view_new_with_buffer(context->log.buffer); |
959 | gtk_text_view_set_editable(GTK_TEXT_VIEW(context->log.view), FALSE); |
960 | gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(context->log.view), FALSE); |
961 | gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(context->log.view), GTK_WRAP_WORD); |
962 | |
963 | gtk_container_add(GTK_CONTAINER(scrolled_window), context->log.view); |
964 | gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window), |
965 | GTK_SHADOW_IN); |
966 | |
967 | gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(context->dialog)->vbox), |
968 | scrolled_window); |
969 | gtk_widget_show_all(context->dialog); |
970 | |
971 | /* server url should not end with a slash */ |
972 | if(project->server[strlen(project->server)-1] == '/') { |
973 | printf("removing trailing slash\n"); |
974 | project->server[strlen(project->server)-1] = 0; |
975 | project->dirty = TRUE; /* project needs to be saved */ |
976 | } |
977 | |
978 | appendf(&context->log, NULL, _("Log generated by %s v%s using API 0.6\n"), |
979 | PACKAGE, VERSION); |
980 | appendf(&context->log, NULL, _("User comment: %s\n"), context->comment); |
981 | |
982 | g_assert(project->server); |
983 | |
984 | /* check if server name contains string "0.5" and adjust it */ |
985 | if(strstr(project->server, "0.5") != NULL) { |
986 | strstr(project->server, "0.5")[2] = '6'; |
987 | |
988 | appendf(&context->log, NULL, _("Adjusting server name to v0.6\n")); |
989 | project->dirty = TRUE; // project needs to be changed |
990 | } |
991 | |
992 | appendf(&context->log, NULL, _("Uploading to %s\n"), project->server); |
993 | |
994 | /* create a new changeset */ |
995 | if(osm_create_changeset(context)) { |
996 | /* check for dirty entries */ |
997 | appendf(&context->log, NULL, _("Uploading nodes:\n")); |
998 | osm_upload_nodes(context); |
999 | appendf(&context->log, NULL, _("Uploading ways:\n")); |
1000 | osm_upload_ways(context); |
1001 | appendf(&context->log, NULL, _("Uploading relations:\n")); |
1002 | osm_upload_relations(context); |
1003 | appendf(&context->log, NULL, _("Deleting relations:\n")); |
1004 | osm_delete_relations(context); |
1005 | appendf(&context->log, NULL, _("Deleting ways:\n")); |
1006 | osm_delete_ways(context); |
1007 | appendf(&context->log, NULL, _("Deleting nodes:\n")); |
1008 | osm_delete_nodes(context); |
1009 | |
1010 | /* close changeset */ |
1011 | osm_close_changeset(context); |
1012 | } |
1013 | |
1014 | appendf(&context->log, NULL, _("Upload done.\n")); |
1015 | |
1016 | gboolean reload_map = FALSE; |
1017 | if(project->data_dirty) { |
1018 | appendf(&context->log, NULL, _("Server data has been modified.\n")); |
1019 | appendf(&context->log, NULL, _("Downloading updated osm data ...\n")); |
1020 | |
1021 | if(osm_download(context->dialog, appdata->settings, project)) { |
1022 | appendf(&context->log, NULL, _("Download successful!\n")); |
1023 | appendf(&context->log, NULL, _("The map will be reloaded.\n")); |
1024 | project->data_dirty = FALSE; |
1025 | reload_map = TRUE; |
1026 | } else |
1027 | appendf(&context->log, NULL, _("Download failed!\n")); |
1028 | |
1029 | project_save(context->dialog, project); |
1030 | |
1031 | if(reload_map) { |
1032 | /* this kind of rather brute force reload is useful as the moment */ |
1033 | /* after the upload is a nice moment to bring everything in sync again. */ |
1034 | /* we basically restart the entire map with fresh data from the server */ |
1035 | /* and the diff will hopefully be empty (if the upload was successful) */ |
1036 | |
1037 | appendf(&context->log, NULL, _("Reloading map ...\n")); |
1038 | |
1039 | if(!diff_is_clean(appdata->osm, FALSE)) { |
1040 | appendf(&context->log, COLOR_ERR, _("*** DIFF IS NOT CLEAN ***\n")); |
1041 | appendf(&context->log, COLOR_ERR, _("Something went wrong during upload,\n")); |
1042 | appendf(&context->log, COLOR_ERR, _("proceed with care!\n")); |
1043 | } |
1044 | |
1045 | /* redraw the entire map by destroying all map items and redrawing them */ |
1046 | appendf(&context->log, NULL, _("Cleaning up ...\n")); |
1047 | diff_save(appdata->project, appdata->osm); |
1048 | map_clear(appdata, MAP_LAYER_OBJECTS_ONLY); |
1049 | osm_free(&appdata->icon, appdata->osm); |
1050 | |
1051 | appendf(&context->log, NULL, _("Loading OSM ...\n")); |
1052 | appdata->osm = osm_parse(appdata->project->path, appdata->project->osm); |
1053 | appendf(&context->log, NULL, _("Applying diff ...\n")); |
1054 | diff_restore(appdata, appdata->project, appdata->osm); |
1055 | appendf(&context->log, NULL, _("Painting ...\n")); |
1056 | map_paint(appdata); |
1057 | appendf(&context->log, NULL, _("Done!\n")); |
1058 | } |
1059 | } |
1060 | |
1061 | /* tell the user that he can stop waiting ... */ |
1062 | appendf(&context->log, NULL, _("Process finished.\n")); |
1063 | |
1064 | gtk_dialog_set_response_sensitive(GTK_DIALOG(context->dialog), |
1065 | GTK_RESPONSE_CLOSE, TRUE); |
1066 | |
1067 | gtk_dialog_run(GTK_DIALOG(context->dialog)); |
1068 | gtk_widget_destroy(context->dialog); |
1069 | |
1070 | if(context->comment) g_free(context->comment); |
1071 | g_free(context); |
1072 | } |
1073 |