Parent Directory | Revision Log
Begin trunk. No code changes.
1 | harbaum | 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 |