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