Parent Directory | Revision Log
Initial import
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 | static struct http_message_s { |
28 | int id; |
29 | char *msg; |
30 | } http_messages [] = { |
31 | { 200, "Ok" }, |
32 | { 400, "Bad Request" }, |
33 | { 401, "Unauthorized" }, |
34 | { 403, "Forbidden" }, |
35 | { 404, "Not Found" }, |
36 | { 405, "Method Not Allowed" }, |
37 | { 410, "Gone" }, |
38 | { 412, "Precondition Failed" }, |
39 | { 417, "(Expect rejected)" }, |
40 | { 500, "Internal Server Error" }, |
41 | { 503, "Service Unavailable" }, |
42 | { 0, NULL } |
43 | }; |
44 | |
45 | static char *osm_http_message(int id) { |
46 | struct http_message_s *msg = http_messages; |
47 | |
48 | while(msg->id) { |
49 | if(msg->id == id) return _(msg->msg); |
50 | msg++; |
51 | } |
52 | |
53 | return NULL; |
54 | } |
55 | |
56 | typedef struct { |
57 | GtkWidget *wait_dialog; |
58 | GtkWidget *pbar; |
59 | char *url, *filename; |
60 | gboolean cancelled; |
61 | CURLcode res; |
62 | long response; |
63 | char buffer[CURL_ERROR_SIZE]; |
64 | |
65 | int percent; |
66 | } osm_download_context_t; |
67 | |
68 | typedef struct { |
69 | appdata_t *appdata; |
70 | GtkWidget *dialog; |
71 | osm_t *osm; |
72 | project_t *project; |
73 | |
74 | struct log_s { |
75 | GtkTextBuffer *buffer; |
76 | GtkWidget *view; |
77 | } log; |
78 | |
79 | } osm_upload_context_t; |
80 | |
81 | #if 0 // todo: figure out how to stop a curl transfer |
82 | /* Our usual callback function */ |
83 | static void on_cancel(GtkWidget *widget, gpointer data) { |
84 | osm_download_context_t *context = (osm_download_context_t*)data; |
85 | context->cancelled = TRUE; |
86 | } |
87 | #endif |
88 | |
89 | static int my_progress_func(osm_download_context_t *context, |
90 | double t, /* dltotal */ double d, /* dlnow */ |
91 | double ultotal, double ulnow) { |
92 | // printf("%f / %f (%g %%)\n", d, t, d*100.0/t); |
93 | if(t) context->percent = d*100.0/t; |
94 | else context->percent = 0; |
95 | |
96 | return 0; |
97 | } |
98 | |
99 | static void *my_thread(void *ptr) { |
100 | osm_download_context_t *context = (osm_download_context_t*)ptr; |
101 | CURL *curl; |
102 | FILE *outfile; |
103 | |
104 | curl = curl_easy_init(); |
105 | if(curl) { |
106 | outfile = fopen(context->filename, "w"); |
107 | if(outfile) { |
108 | curl_easy_setopt(curl, CURLOPT_URL, context->url); |
109 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, outfile); |
110 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); |
111 | curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); |
112 | curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, my_progress_func); |
113 | curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, context); |
114 | curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, context->buffer); |
115 | |
116 | context->res = curl_easy_perform(curl); |
117 | printf("curl perform returned with %d\n", context->res); |
118 | |
119 | curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &context->response); |
120 | |
121 | fclose(outfile); |
122 | } |
123 | |
124 | /* always cleanup */ |
125 | curl_easy_cleanup(curl); |
126 | } |
127 | |
128 | printf("thread exiting\n"); |
129 | context->percent = 100; |
130 | |
131 | return NULL; |
132 | } |
133 | |
134 | static gint wait_dialog_destroy_event(GtkWidget *widget, gpointer data) { |
135 | osm_download_context_t *context = (osm_download_context_t*)data; |
136 | |
137 | printf("destroying wait dialog\n"); |
138 | |
139 | context->wait_dialog = NULL; |
140 | |
141 | /* todo: terminate http transfer!! */ |
142 | |
143 | return FALSE; |
144 | } |
145 | |
146 | gboolean osm_download(GtkWidget *parent, project_t *project) { |
147 | printf("download ...\n"); |
148 | |
149 | /* ------------ busy dialog -------------- */ |
150 | osm_download_context_t *context = g_new0(osm_download_context_t, 1); |
151 | char minlon[G_ASCII_DTOSTR_BUF_SIZE], minlat[G_ASCII_DTOSTR_BUF_SIZE]; |
152 | char maxlon[G_ASCII_DTOSTR_BUF_SIZE], maxlat[G_ASCII_DTOSTR_BUF_SIZE]; |
153 | |
154 | g_ascii_dtostr(minlon, sizeof(minlon), project->min.lon); |
155 | g_ascii_dtostr(minlat, sizeof(minlat), project->min.lat); |
156 | g_ascii_dtostr(maxlon, sizeof(maxlon), project->max.lon); |
157 | g_ascii_dtostr(maxlat, sizeof(maxlat), project->max.lat); |
158 | |
159 | context->url = g_strdup_printf("%s/map?bbox=%s,%s,%s,%s", |
160 | project->server, minlon, minlat, maxlon, maxlat); |
161 | |
162 | context->filename = strdup(project->osm); |
163 | printf("going to fetch %s to %s\n", context->url, context->filename); |
164 | |
165 | context->wait_dialog = gtk_dialog_new(); |
166 | gtk_dialog_set_has_separator(GTK_DIALOG(context->wait_dialog), FALSE); |
167 | gtk_window_set_title(GTK_WINDOW(context->wait_dialog), _("Downloading...")); |
168 | gtk_window_set_default_size(GTK_WINDOW(context->wait_dialog), 300, 10); |
169 | |
170 | gtk_window_set_modal(GTK_WINDOW(context->wait_dialog), TRUE); |
171 | gtk_window_set_transient_for(GTK_WINDOW(context->wait_dialog), |
172 | GTK_WINDOW(parent)); |
173 | GtkAdjustment *adj = (GtkAdjustment*)gtk_adjustment_new(0, 0, 100, 0, 0, 0); |
174 | context->pbar = gtk_progress_bar_new_with_adjustment(adj); |
175 | gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(context->wait_dialog)->vbox), |
176 | context->pbar); |
177 | |
178 | #if 0 |
179 | GtkWidget *button = gtk_button_new_with_label(_("Cancel")); |
180 | gtk_signal_connect(GTK_OBJECT(button), "clicked", |
181 | GTK_SIGNAL_FUNC(on_cancel), (gpointer)context); |
182 | gtk_container_add(GTK_CONTAINER(GTK_DIALOG(context->wait_dialog)-> |
183 | action_area), button); |
184 | #endif |
185 | |
186 | gtk_signal_connect(GTK_OBJECT(context->wait_dialog), |
187 | "destroy", G_CALLBACK(wait_dialog_destroy_event), context); |
188 | |
189 | gtk_widget_show_all(context->wait_dialog); |
190 | |
191 | if(!g_thread_create(&my_thread, context, FALSE, NULL) != 0) |
192 | g_warning("can't create the thread"); |
193 | |
194 | /* wait for download to finish */ |
195 | int percent = -1; |
196 | while(context->wait_dialog) { |
197 | if(context->percent != percent) { |
198 | gtk_progress_set_value(GTK_PROGRESS(context->pbar), |
199 | context->percent); |
200 | |
201 | percent = context->percent; |
202 | |
203 | printf("context->percent = %d\n", context->percent); |
204 | if(percent == 100) |
205 | gtk_widget_destroy(context->wait_dialog); |
206 | } else |
207 | usleep(1000000); |
208 | |
209 | while(gtk_events_pending()) |
210 | gtk_main_iteration(); |
211 | } |
212 | |
213 | gboolean result = (context->res == 0); |
214 | if(!result) |
215 | errorf(parent, _("Download failed with message:\n\n%s"), context->buffer); |
216 | |
217 | if(context->response != 200) { |
218 | errorf(parent, _("Download failed with code %ld:\n\n%s\n"), |
219 | context->response, osm_http_message(context->response)); |
220 | g_remove(context->filename); |
221 | } |
222 | |
223 | printf("clean up\n"); |
224 | /* clean up */ |
225 | if(context->url) g_free(context->url); |
226 | g_free(context); |
227 | |
228 | return result; |
229 | } |
230 | |
231 | typedef struct { |
232 | char *ptr; |
233 | int len; |
234 | } curl_data_t; |
235 | |
236 | static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *stream) { |
237 | curl_data_t *p = (curl_data_t*)stream; |
238 | |
239 | // printf("request to read %d items of size %d, pointer = %p\n", |
240 | // nmemb, size, p->ptr); |
241 | |
242 | if(nmemb*size > p->len) |
243 | nmemb = p->len/size; |
244 | |
245 | memcpy(ptr, p->ptr, size*nmemb); |
246 | p->ptr += size*nmemb; |
247 | p->len -= size*nmemb; |
248 | |
249 | return nmemb; |
250 | } |
251 | |
252 | static size_t write_callback(void *ptr, size_t size, size_t nmemb, void *stream) { |
253 | curl_data_t *p = (curl_data_t*)stream; |
254 | |
255 | p->ptr = g_realloc(p->ptr, p->len + size*nmemb + 1); |
256 | if(p->ptr) { |
257 | memcpy(p->ptr+p->len, ptr, size*nmemb); |
258 | p->len += size*nmemb; |
259 | p->ptr[p->len] = 0; |
260 | } |
261 | return nmemb; |
262 | } |
263 | |
264 | static void appendf(struct log_s *log, const char *fmt, ...) { |
265 | va_list args; |
266 | va_start( args, fmt ); |
267 | char *buf = g_strdup_vprintf(fmt, args); |
268 | va_end( args ); |
269 | |
270 | GtkTextIter end; |
271 | gtk_text_buffer_get_end_iter(log->buffer, &end); |
272 | gtk_text_buffer_insert(log->buffer, &end, buf, -1); |
273 | |
274 | g_free(buf); |
275 | |
276 | gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(log->view), |
277 | &end, 0.0, FALSE, 0, 0); |
278 | |
279 | while(gtk_events_pending()) |
280 | gtk_main_iteration(); |
281 | } |
282 | |
283 | #define MAX_TRY 5 |
284 | |
285 | static gboolean osm_update_item(struct log_s *log, char *xml_str, |
286 | char *url, char *user, item_id_t *id) { |
287 | int retry = MAX_TRY; |
288 | char buffer[CURL_ERROR_SIZE]; |
289 | |
290 | CURL *curl; |
291 | CURLcode res; |
292 | |
293 | curl_data_t read_data; |
294 | curl_data_t write_data; |
295 | |
296 | while(retry >= 0) { |
297 | |
298 | if(retry != MAX_TRY) |
299 | appendf(log, _("Retry %d/%d "), MAX_TRY-retry, MAX_TRY-1); |
300 | |
301 | /* get a curl handle */ |
302 | curl = curl_easy_init(); |
303 | if(!curl) { |
304 | appendf(log, _("CURL init error\n")); |
305 | return FALSE; |
306 | } |
307 | |
308 | read_data.ptr = xml_str; |
309 | read_data.len = strlen(xml_str); |
310 | write_data.ptr = NULL; |
311 | write_data.len = 0; |
312 | |
313 | /* we want to use our own read/write functions */ |
314 | curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback); |
315 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); |
316 | |
317 | /* enable uploading */ |
318 | curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); |
319 | |
320 | /* specify target URL, and note that this URL should include a file |
321 | name, not only a directory */ |
322 | curl_easy_setopt(curl, CURLOPT_URL, url); |
323 | |
324 | /* now specify which file to upload */ |
325 | curl_easy_setopt(curl, CURLOPT_READDATA, &read_data); |
326 | |
327 | /* provide the size of the upload, we specicially typecast the value |
328 | to curl_off_t since we must be sure to use the correct data size */ |
329 | curl_easy_setopt(curl, CURLOPT_INFILESIZE, (long)strlen(xml_str)); |
330 | |
331 | /* we pass our 'chunk' struct to the callback function */ |
332 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &write_data); |
333 | |
334 | /* some servers don't like requests that are made without a user-agent |
335 | field, so we provide one */ |
336 | curl_easy_setopt(curl, CURLOPT_USERAGENT, |
337 | PACKAGE "-libcurl/" VERSION); |
338 | |
339 | struct curl_slist *slist=NULL; |
340 | slist = curl_slist_append(slist, "Expect:"); |
341 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); |
342 | |
343 | curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, buffer); |
344 | |
345 | /* set user name and password for the authentication */ |
346 | curl_easy_setopt(curl, CURLOPT_USERPWD, user); |
347 | |
348 | /* Now run off and do what you've been told! */ |
349 | res = curl_easy_perform(curl); |
350 | |
351 | long response; |
352 | curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response); |
353 | |
354 | /* always cleanup */ |
355 | curl_slist_free_all(slist); |
356 | curl_easy_cleanup(curl); |
357 | |
358 | printf("reply is \"%s\"\n", write_data.ptr); |
359 | |
360 | /* this will return the id on a successful create */ |
361 | if(id && (res == 0) && (response == 200)) { |
362 | printf("request to parse successful reply as an id\n"); |
363 | *id = strtoul(write_data.ptr, NULL, 10); |
364 | } |
365 | |
366 | g_free(write_data.ptr); |
367 | |
368 | if(res != 0) |
369 | appendf(log, _("failed: %s\n"), buffer); |
370 | else if(response != 200) |
371 | appendf(log, _("failed, code: %ld %s\n"), |
372 | response, osm_http_message(response)); |
373 | else { |
374 | if(!id) appendf(log, _("ok\n")); |
375 | else appendf(log, _("ok: #%ld\n"), *id); |
376 | } |
377 | |
378 | /* don't retry unless we had an "internal server error" */ |
379 | if(response != 500) |
380 | return((res == 0)&&(response == 200)); |
381 | |
382 | retry--; |
383 | } |
384 | |
385 | return FALSE; |
386 | } |
387 | |
388 | static gboolean osm_delete_item(struct log_s *log, char *url, char *user) { |
389 | int retry = MAX_TRY; |
390 | char buffer[CURL_ERROR_SIZE]; |
391 | |
392 | CURL *curl; |
393 | CURLcode res; |
394 | |
395 | while(retry >= 0) { |
396 | |
397 | if(retry != MAX_TRY) |
398 | appendf(log, _("Retry %d/%d "), MAX_TRY-retry, MAX_TRY-1); |
399 | |
400 | /* get a curl handle */ |
401 | curl = curl_easy_init(); |
402 | if(!curl) { |
403 | appendf(log, _("CURL init error\n")); |
404 | return FALSE; |
405 | } |
406 | |
407 | /* no read/write functions required */ |
408 | curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); |
409 | |
410 | /* specify target URL, and note that this URL should include a file |
411 | name, not only a directory */ |
412 | curl_easy_setopt(curl, CURLOPT_URL, url); |
413 | |
414 | /* some servers don't like requests that are made without a user-agent |
415 | field, so we provide one */ |
416 | curl_easy_setopt(curl, CURLOPT_USERAGENT, PACKAGE "-libcurl/" VERSION); |
417 | |
418 | curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, buffer); |
419 | |
420 | /* set user name and password for the authentication */ |
421 | curl_easy_setopt(curl, CURLOPT_USERPWD, user); |
422 | |
423 | /* Now run off and do what you've been told! */ |
424 | res = curl_easy_perform(curl); |
425 | |
426 | long response; |
427 | curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response); |
428 | |
429 | /* always cleanup */ |
430 | curl_easy_cleanup(curl); |
431 | |
432 | if(res != 0) |
433 | appendf(log, _("failed: %s\n"), buffer); |
434 | else if(response != 200) |
435 | appendf(log, _("failed, code: %ld %s\n"), |
436 | response, osm_http_message(response)); |
437 | else |
438 | appendf(log, _("ok\n")); |
439 | |
440 | /* don't retry unless we had an "internal server error" */ |
441 | if(response != 500) |
442 | return((res == 0)&&(response == 200)); |
443 | |
444 | retry--; |
445 | } |
446 | |
447 | return FALSE; |
448 | } |
449 | |
450 | typedef struct { |
451 | struct { |
452 | int total, new, dirty, deleted; |
453 | } ways, nodes, relations; |
454 | } osm_dirty_t; |
455 | |
456 | static GtkWidget *table_attach_label_c(GtkWidget *table, char *str, |
457 | int x1, int x2, int y1, int y2) { |
458 | GtkWidget *label = gtk_label_new(str); |
459 | gtk_table_attach_defaults(GTK_TABLE(table), label, x1, x2, y1, y2); |
460 | return label; |
461 | } |
462 | |
463 | static GtkWidget *table_attach_label_l(GtkWidget *table, char *str, |
464 | int x1, int x2, int y1, int y2) { |
465 | GtkWidget *label = table_attach_label_c(table, str, x1, x2, y1, y2); |
466 | gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f); |
467 | return label; |
468 | } |
469 | |
470 | static GtkWidget *table_attach_int(GtkWidget *table, int num, |
471 | int x1, int x2, int y1, int y2) { |
472 | char *str = g_strdup_printf("%d", num); |
473 | GtkWidget *label = table_attach_label_c(table, str, x1, x2, y1, y2); |
474 | g_free(str); |
475 | return label; |
476 | } |
477 | |
478 | static void osm_delete_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_DELETED) { |
487 | printf("deleting node on server\n"); |
488 | |
489 | appendf(&context->log, _("Delete node #%ld "), node->id); |
490 | |
491 | char *url = g_strdup_printf("%s/node/%lu", project->server, node->id); |
492 | char *cred = g_strdup_printf("%s:%s", |
493 | context->appdata->settings->username, |
494 | context->appdata->settings->password); |
495 | |
496 | if(osm_delete_item(&context->log, url, cred)) { |
497 | node->flags &= ~(OSM_FLAG_DIRTY | OSM_FLAG_DELETED); |
498 | project->data_dirty = TRUE; |
499 | } |
500 | |
501 | g_free(cred); |
502 | } |
503 | node = node->next; |
504 | } |
505 | } |
506 | |
507 | static void osm_upload_nodes(osm_upload_context_t *context) { |
508 | node_t *node = context->osm->node; |
509 | project_t *project = context->project; |
510 | |
511 | while(node) { |
512 | /* make sure gui gets updated */ |
513 | while(gtk_events_pending()) gtk_main_iteration(); |
514 | |
515 | if((node->flags & (OSM_FLAG_DIRTY | OSM_FLAG_NEW)) && |
516 | (!(node->flags & OSM_FLAG_DELETED))) { |
517 | char *url = NULL; |
518 | |
519 | if(node->flags & OSM_FLAG_NEW) { |
520 | url = g_strdup_printf("%s/node/create", project->server); |
521 | appendf(&context->log, _("New node ")); |
522 | } else { |
523 | url = g_strdup_printf("%s/node/%lu", project->server, node->id); |
524 | appendf(&context->log, _("Modified node #%ld "), node->id); |
525 | } |
526 | |
527 | /* upload this node */ |
528 | char *xml_str = osm_generate_xml_node(context->osm, node); |
529 | if(xml_str) { |
530 | printf("uploading node %s from address %p\n", url, xml_str); |
531 | |
532 | char *cred = g_strdup_printf("%s:%s", |
533 | context->appdata->settings->username, context->appdata->settings->password); |
534 | if(osm_update_item(&context->log, xml_str, url, cred, |
535 | (node->flags & OSM_FLAG_NEW)?&(node->id):NULL)) { |
536 | |
537 | node->flags &= ~(OSM_FLAG_DIRTY | OSM_FLAG_NEW); |
538 | project->data_dirty = TRUE; |
539 | } |
540 | g_free(cred); |
541 | } |
542 | g_free(url); |
543 | } |
544 | node = node->next; |
545 | } |
546 | } |
547 | |
548 | static void osm_delete_ways(osm_upload_context_t *context) { |
549 | way_t *way = context->osm->way; |
550 | project_t *project = context->project; |
551 | |
552 | while(way) { |
553 | /* make sure gui gets updated */ |
554 | while(gtk_events_pending()) gtk_main_iteration(); |
555 | |
556 | if(way->flags & OSM_FLAG_DELETED) { |
557 | printf("deleting way on server\n"); |
558 | |
559 | appendf(&context->log, _("Delete way #%ld "), way->id); |
560 | |
561 | char *url = g_strdup_printf("%s/way/%lu", project->server, way->id); |
562 | char *cred = g_strdup_printf("%s:%s", |
563 | context->appdata->settings->username, context->appdata->settings->password); |
564 | |
565 | if(osm_delete_item(&context->log, url, cred)) { |
566 | way->flags &= ~(OSM_FLAG_DIRTY | OSM_FLAG_DELETED); |
567 | project->data_dirty = TRUE; |
568 | } |
569 | |
570 | g_free(cred); |
571 | } |
572 | way = way->next; |
573 | } |
574 | } |
575 | |
576 | |
577 | static void osm_upload_ways(osm_upload_context_t *context) { |
578 | way_t *way = context->osm->way; |
579 | project_t *project = context->project; |
580 | |
581 | while(way) { |
582 | /* make sure gui gets updated */ |
583 | while(gtk_events_pending()) gtk_main_iteration(); |
584 | |
585 | if((way->flags & (OSM_FLAG_DIRTY | OSM_FLAG_NEW)) && |
586 | (!(way->flags & OSM_FLAG_DELETED))) { |
587 | char *url = NULL; |
588 | |
589 | if(way->flags & OSM_FLAG_NEW) { |
590 | url = g_strdup_printf("%s/way/create", project->server); |
591 | appendf(&context->log, _("New way ")); |
592 | } else { |
593 | url = g_strdup_printf("%s/way/%lu", project->server, way->id); |
594 | appendf(&context->log, _("Modified way #%ld "), way->id); |
595 | } |
596 | |
597 | /* upload this node */ |
598 | char *xml_str = osm_generate_xml_way(context->osm, way); |
599 | if(xml_str) { |
600 | printf("uploading way %s from address %p\n", url, xml_str); |
601 | |
602 | char *cred = g_strdup_printf("%s:%s", |
603 | context->appdata->settings->username, context->appdata->settings->password); |
604 | if(osm_update_item(&context->log, xml_str, url, cred, |
605 | (way->flags & OSM_FLAG_NEW)?&(way->id):NULL)) { |
606 | way->flags &= ~(OSM_FLAG_DIRTY | OSM_FLAG_NEW); |
607 | project->data_dirty = TRUE; |
608 | } |
609 | g_free(cred); |
610 | } |
611 | g_free(url); |
612 | } |
613 | way = way->next; |
614 | } |
615 | } |
616 | |
617 | |
618 | static void osm_upload_relations(osm_upload_context_t *context) { |
619 | relation_t *relation = context->osm->relation; |
620 | project_t *project = context->project; |
621 | |
622 | while(relation) { |
623 | /* make sure gui gets updated */ |
624 | while(gtk_events_pending()) gtk_main_iteration(); |
625 | |
626 | if((relation->flags & (OSM_FLAG_DIRTY | OSM_FLAG_NEW)) && |
627 | (!(relation->flags & OSM_FLAG_DELETED))) { |
628 | char *url = NULL; |
629 | |
630 | if(relation->flags & OSM_FLAG_NEW) { |
631 | url = g_strdup_printf("%s/relation/create", project->server); |
632 | appendf(&context->log, _("New relation ")); |
633 | } else { |
634 | url = g_strdup_printf("%s/relation/%lu", project->server,relation->id); |
635 | appendf(&context->log, _("Modified relation #%ld "), relation->id); |
636 | } |
637 | |
638 | /* upload this relation */ |
639 | char *xml_str = osm_generate_xml_relation(context->osm, relation); |
640 | if(xml_str) { |
641 | printf("uploading relation %s from address %p\n", url, xml_str); |
642 | |
643 | char *cred = g_strdup_printf("%s:%s", |
644 | context->appdata->settings->username, context->appdata->settings->password); |
645 | if(osm_update_item(&context->log, xml_str, url, cred, |
646 | (relation->flags & OSM_FLAG_NEW)?&(relation->id):NULL)) { |
647 | relation->flags &= ~(OSM_FLAG_DIRTY | OSM_FLAG_NEW); |
648 | project->data_dirty = TRUE; |
649 | } |
650 | g_free(cred); |
651 | } |
652 | g_free(url); |
653 | } |
654 | relation = relation->next; |
655 | } |
656 | } |
657 | |
658 | |
659 | void osm_upload(appdata_t *appdata, osm_t *osm, project_t *project) { |
660 | |
661 | printf("starting upload\n"); |
662 | |
663 | /* upload config and confirmation dialog */ |
664 | |
665 | /* count nodes */ |
666 | osm_dirty_t dirty; |
667 | memset(&dirty, 0, sizeof(osm_dirty_t)); |
668 | |
669 | node_t *node = osm->node; |
670 | while(node) { |
671 | dirty.nodes.total++; |
672 | if(node->flags & OSM_FLAG_DELETED) dirty.nodes.deleted++; |
673 | else if(node->flags & OSM_FLAG_NEW) dirty.nodes.new++; |
674 | else if(node->flags & OSM_FLAG_DIRTY) dirty.nodes.dirty++; |
675 | |
676 | node = node->next; |
677 | } |
678 | printf("nodes: new %2d, dirty %2d, deleted %2d\n", |
679 | dirty.nodes.new, dirty.nodes.dirty, dirty.nodes.deleted); |
680 | |
681 | /* count ways */ |
682 | way_t *way = osm->way; |
683 | while(way) { |
684 | dirty.ways.total++; |
685 | if(way->flags & OSM_FLAG_DELETED) dirty.ways.deleted++; |
686 | else if(way->flags & OSM_FLAG_NEW) dirty.ways.new++; |
687 | else if(way->flags & OSM_FLAG_DIRTY) dirty.ways.dirty++; |
688 | |
689 | way = way->next; |
690 | } |
691 | printf("ways: new %2d, dirty %2d, deleted %2d\n", |
692 | dirty.ways.new, dirty.ways.dirty, dirty.ways.deleted); |
693 | |
694 | /* count relations */ |
695 | relation_t *relation = osm->relation; |
696 | while(relation) { |
697 | dirty.relations.total++; |
698 | if(relation->flags & OSM_FLAG_DELETED) dirty.relations.deleted++; |
699 | else if(relation->flags & OSM_FLAG_NEW) dirty.relations.new++; |
700 | else if(relation->flags & OSM_FLAG_DIRTY) dirty.relations.dirty++; |
701 | |
702 | relation = relation->next; |
703 | } |
704 | printf("relations: new %2d, dirty %2d, deleted %2d\n", |
705 | dirty.relations.new, dirty.relations.dirty, dirty.relations.deleted); |
706 | |
707 | |
708 | GtkWidget *dialog = gtk_dialog_new_with_buttons(_("Upload to OSM"), |
709 | GTK_WINDOW(appdata->window), GTK_DIALOG_MODAL, |
710 | GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, |
711 | GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, |
712 | NULL); |
713 | |
714 | GtkWidget *table = gtk_table_new(4, 5, TRUE); |
715 | |
716 | table_attach_label_c(table, _("Total"), 1, 2, 0, 1); |
717 | table_attach_label_c(table, _("New"), 2, 3, 0, 1); |
718 | table_attach_label_c(table, _("Modified"), 3, 4, 0, 1); |
719 | table_attach_label_c(table, _("Deleted"), 4, 5, 0, 1); |
720 | |
721 | table_attach_label_l(table, _("Nodes:"), 0, 1, 1, 2); |
722 | table_attach_int(table, dirty.nodes.total, 1, 2, 1, 2); |
723 | table_attach_int(table, dirty.nodes.new, 2, 3, 1, 2); |
724 | table_attach_int(table, dirty.nodes.dirty, 3, 4, 1, 2); |
725 | table_attach_int(table, dirty.nodes.deleted, 4, 5, 1, 2); |
726 | |
727 | table_attach_label_l(table, _("Ways:"), 0, 1, 2, 3); |
728 | table_attach_int(table, dirty.ways.total, 1, 2, 2, 3); |
729 | table_attach_int(table, dirty.ways.new, 2, 3, 2, 3); |
730 | table_attach_int(table, dirty.ways.dirty, 3, 4, 2, 3); |
731 | table_attach_int(table, dirty.ways.deleted, 4, 5, 2, 3); |
732 | |
733 | table_attach_label_l(table, _("Relations:"), 0, 1, 3, 4); |
734 | table_attach_int(table, dirty.relations.total, 1, 2, 3, 4); |
735 | table_attach_int(table, dirty.relations.new, 2, 3, 3, 4); |
736 | table_attach_int(table, dirty.relations.dirty, 3, 4, 3, 4); |
737 | table_attach_int(table, dirty.relations.deleted, 4, 5, 3, 4); |
738 | |
739 | gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), table); |
740 | |
741 | /* ------------------------------------------------------ */ |
742 | |
743 | gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), |
744 | gtk_hseparator_new()); |
745 | |
746 | /* ------- add username and password entries ------------ */ |
747 | |
748 | table = gtk_table_new(2, 2, FALSE); |
749 | table_attach_label_l(table, _("Username:"), 0, 1, 0, 1); |
750 | GtkWidget *uentry = gtk_entry_new(); |
751 | HILDON_ENTRY_NO_AUTOCAP(uentry); |
752 | gtk_entry_set_text(GTK_ENTRY(uentry), appdata->settings->username); |
753 | gtk_table_attach_defaults(GTK_TABLE(table), uentry, 1, 2, 0, 1); |
754 | table_attach_label_l(table, _("Password:"), 0, 1, 1, 2); |
755 | GtkWidget *pentry = gtk_entry_new(); |
756 | HILDON_ENTRY_NO_AUTOCAP(pentry); |
757 | gtk_entry_set_text(GTK_ENTRY(pentry), appdata->settings->password); |
758 | gtk_entry_set_visibility(GTK_ENTRY(pentry), FALSE); |
759 | gtk_table_attach_defaults(GTK_TABLE(table), pentry, 1, 2, 1, 2); |
760 | gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), table); |
761 | |
762 | gtk_widget_show_all(dialog); |
763 | |
764 | if(GTK_RESPONSE_ACCEPT != gtk_dialog_run(GTK_DIALOG(dialog))) { |
765 | printf("upload cancelled\n"); |
766 | gtk_widget_destroy(dialog); |
767 | return; |
768 | } |
769 | |
770 | printf("clicked ok\n"); |
771 | |
772 | /* retrieve username and password */ |
773 | if(appdata->settings->username) |
774 | g_free(appdata->settings->username); |
775 | appdata->settings->username = |
776 | g_strdup(gtk_entry_get_text(GTK_ENTRY(uentry))); |
777 | |
778 | if(appdata->settings->password) |
779 | g_free(appdata->settings->password); |
780 | appdata->settings->password = |
781 | g_strdup(gtk_entry_get_text(GTK_ENTRY(pentry))); |
782 | |
783 | gtk_widget_destroy(dialog); |
784 | project_save(GTK_WIDGET(appdata->window), project); |
785 | |
786 | /* osm upload itself also has a gui */ |
787 | osm_upload_context_t *context = g_new0(osm_upload_context_t, 1); |
788 | context->appdata = appdata; |
789 | context->osm = osm; |
790 | context->project = project; |
791 | |
792 | context->dialog = gtk_dialog_new_with_buttons(_("Upload to OSM"), |
793 | GTK_WINDOW(appdata->window), GTK_DIALOG_MODAL, |
794 | GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL); |
795 | gtk_dialog_set_response_sensitive(GTK_DIALOG(context->dialog), |
796 | GTK_RESPONSE_CLOSE, FALSE); |
797 | |
798 | /* making the dialog a little wider makes it less "crowded" */ |
799 | #ifndef USE_HILDON |
800 | gtk_window_set_default_size(GTK_WINDOW(context->dialog), 480, 256); |
801 | #else |
802 | gtk_window_set_default_size(GTK_WINDOW(context->dialog), 800, 480); |
803 | #endif |
804 | |
805 | /* ------- main ui elelent is this text view --------------- */ |
806 | |
807 | GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL); |
808 | gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), |
809 | GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); |
810 | |
811 | context->log.buffer = gtk_text_buffer_new(NULL); |
812 | |
813 | context->log.view = gtk_text_view_new_with_buffer(context->log.buffer); |
814 | gtk_text_view_set_editable(GTK_TEXT_VIEW(context->log.view), FALSE); |
815 | gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(context->log.view), FALSE); |
816 | |
817 | gtk_container_add(GTK_CONTAINER(scrolled_window), context->log.view); |
818 | gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window), |
819 | GTK_SHADOW_IN); |
820 | |
821 | gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(context->dialog)->vbox), |
822 | scrolled_window); |
823 | gtk_widget_show_all(context->dialog); |
824 | |
825 | appendf(&context->log, _("Log generated by %s v%s using API 0.5\n"), |
826 | PACKAGE, VERSION); |
827 | appendf(&context->log, _("Uploading to %s\n"), project->server); |
828 | |
829 | /* check for dirty entries */ |
830 | appendf(&context->log, _("Uploading nodes:\n")); |
831 | osm_upload_nodes(context); |
832 | appendf(&context->log, _("Uploading ways:\n")); |
833 | osm_upload_ways(context); |
834 | appendf(&context->log, _("Uploading relations:\n")); |
835 | osm_upload_relations(context); |
836 | appendf(&context->log, _("Deleting ways:\n")); |
837 | osm_delete_ways(context); |
838 | appendf(&context->log, _("Deleting nodes:\n")); |
839 | osm_delete_nodes(context); |
840 | |
841 | |
842 | appendf(&context->log, _("Upload done.\n")); |
843 | |
844 | gboolean reload_map = FALSE; |
845 | if(project->data_dirty) { |
846 | appendf(&context->log, _("Server data has been modified.\n")); |
847 | appendf(&context->log, _("Downloading updated osm data ...\n")); |
848 | |
849 | if(osm_download(context->dialog, project)) { |
850 | appendf(&context->log, _("Download successful!\n")); |
851 | appendf(&context->log, _("The map will be reloaded.\n")); |
852 | project->data_dirty = FALSE; |
853 | reload_map = TRUE; |
854 | } else |
855 | appendf(&context->log, _("Download failed!\n")); |
856 | |
857 | project_save(context->dialog, project); |
858 | |
859 | if(reload_map) { |
860 | /* this kind of rather brute force reload is useful as the moment */ |
861 | /* after the upload is a nice moment to bring everything in sync again. */ |
862 | /* we basically restart the entire map with fresh data from the server */ |
863 | /* and the diff will hopefully be empty (if the upload was successful) */ |
864 | |
865 | appendf(&context->log, _("Reloading map ...\n")); |
866 | |
867 | if(!diff_is_clean(appdata->osm, FALSE)) { |
868 | appendf(&context->log, _(">>>>>>>> DIFF IS NOT CLEAN <<<<<<<<\n")); |
869 | appendf(&context->log, _("Something went wrong during upload,\n")); |
870 | appendf(&context->log, _("proceed with care!\n")); |
871 | } |
872 | |
873 | /* redraw the entire map by destroying all map items and redrawing them */ |
874 | appendf(&context->log, _("Cleaning up ...\n")); |
875 | diff_save(appdata->project, appdata->osm); |
876 | map_clear(appdata, MAP_LAYER_OBJECTS_ONLY); |
877 | osm_free(&appdata->icon, appdata->osm); |
878 | |
879 | appendf(&context->log, _("Loading OSM ...\n")); |
880 | appdata->osm = osm_parse(appdata->project->osm); |
881 | appendf(&context->log, _("Applying diff ...\n")); |
882 | diff_restore(appdata, appdata->project, appdata->osm); |
883 | appendf(&context->log, _("Painting ...\n")); |
884 | map_paint(appdata); |
885 | appendf(&context->log, _("Done!\n")); |
886 | } |
887 | } |
888 | |
889 | /* tell the user that he can stop waiting ... */ |
890 | appendf(&context->log, _("Process finished.\n")); |
891 | |
892 | gtk_dialog_set_response_sensitive(GTK_DIALOG(context->dialog), |
893 | GTK_RESPONSE_CLOSE, TRUE); |
894 | |
895 | gtk_dialog_run(GTK_DIALOG(context->dialog)); |
896 | gtk_widget_destroy(context->dialog); |
897 | |
898 | } |
899 |