Parent Directory | Revision Log
Removed project dirty flag, removed trailing ... in labels
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 | typedef struct { |
46 | char *ptr; |
47 | int len; |
48 | } curl_mem_t; |
49 | |
50 | typedef enum { NET_IO_DL_FILE, NET_IO_DL_MEM, NET_IO_DELETE } net_io_type_t; |
51 | |
52 | /* structure shared between worker and master thread */ |
53 | typedef struct { |
54 | net_io_type_t type; |
55 | gint refcount; /* reference counter for master and worker thread */ |
56 | |
57 | char *url, *user; |
58 | gboolean cancel; |
59 | float progress; |
60 | |
61 | /* curl/http related stuff: */ |
62 | CURLcode res; |
63 | long response; |
64 | char buffer[CURL_ERROR_SIZE]; |
65 | |
66 | /* request specific fields */ |
67 | union { |
68 | char *filename; /* used for NET_IO_DL_FILE */ |
69 | curl_mem_t mem; /* used for NET_IO_DL_MEM */ |
70 | }; |
71 | |
72 | /* system proxy settings if present */ |
73 | proxy_t *proxy; |
74 | |
75 | } net_io_request_t; |
76 | |
77 | static char *http_message(int id) { |
78 | struct http_message_s *msg = http_messages; |
79 | |
80 | while(msg->id) { |
81 | if(msg->id == id) return _(msg->msg); |
82 | msg++; |
83 | } |
84 | |
85 | return NULL; |
86 | } |
87 | |
88 | static gint dialog_destroy_event(GtkWidget *widget, gpointer data) { |
89 | /* set cancel flag */ |
90 | *(gboolean*)data = TRUE; |
91 | return FALSE; |
92 | } |
93 | |
94 | static void on_cancel(GtkWidget *widget, gpointer data) { |
95 | /* set cancel flag */ |
96 | *(gboolean*)data = TRUE; |
97 | } |
98 | |
99 | /* create the dialog box shown while worker is running */ |
100 | static GtkWidget *busy_dialog(GtkWidget *parent, GtkWidget **pbar, |
101 | gboolean *cancel_ind) { |
102 | GtkWidget *dialog = gtk_dialog_new(); |
103 | |
104 | gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE); |
105 | gtk_window_set_title(GTK_WINDOW(dialog), _("Downloading")); |
106 | gtk_window_set_default_size(GTK_WINDOW(dialog), 300, 10); |
107 | |
108 | gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); |
109 | gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent)); |
110 | |
111 | g_assert(pbar); |
112 | *pbar = gtk_progress_bar_new(); |
113 | gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(*pbar), 0.1); |
114 | |
115 | gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), *pbar); |
116 | |
117 | GtkWidget *button = gtk_button_new_with_label(_("Cancel")); |
118 | gtk_signal_connect(GTK_OBJECT(button), "clicked", |
119 | GTK_SIGNAL_FUNC(on_cancel), (gpointer)cancel_ind); |
120 | gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), button); |
121 | |
122 | gtk_signal_connect(GTK_OBJECT(dialog), "destroy", |
123 | G_CALLBACK(dialog_destroy_event), (gpointer)cancel_ind); |
124 | |
125 | gtk_widget_show_all(dialog); |
126 | |
127 | return dialog; |
128 | } |
129 | |
130 | static void request_free(net_io_request_t *request) { |
131 | /* decrease refcount and only free structure if no references are left */ |
132 | request->refcount--; |
133 | if(request->refcount) { |
134 | printf("still %d references, keeping request\n", request->refcount); |
135 | return; |
136 | } |
137 | |
138 | printf("no references left, freeing request\n"); |
139 | if(request->url) g_free(request->url); |
140 | if(request->user) g_free(request->user); |
141 | |
142 | /* filename is only a valid filename in NET_IO_DL_FILE mode */ |
143 | if(request->type == NET_IO_DL_FILE && request->filename) |
144 | g_free(request->filename); |
145 | |
146 | g_free(request); |
147 | } |
148 | |
149 | static int curl_progress_func(net_io_request_t *request, |
150 | double t, /* dltotal */ double d, /* dlnow */ |
151 | double ultotal, double ulnow) { |
152 | request->progress = t?d/t:0; |
153 | return 0; |
154 | } |
155 | |
156 | static size_t mem_write(void *ptr, size_t size, size_t nmemb, |
157 | void *stream) { |
158 | curl_mem_t *p = (curl_mem_t*)stream; |
159 | |
160 | p->ptr = g_realloc(p->ptr, p->len + size*nmemb + 1); |
161 | if(p->ptr) { |
162 | memcpy(p->ptr+p->len, ptr, size*nmemb); |
163 | p->len += size*nmemb; |
164 | p->ptr[p->len] = 0; |
165 | } |
166 | return nmemb; |
167 | } |
168 | |
169 | void net_io_set_proxy(CURL *curl, proxy_t *proxy) { |
170 | if(proxy) { |
171 | if(proxy->ignore_hosts) |
172 | printf("WARNING: Pproxy \"ignore_hosts\" unsupported!\n"); |
173 | |
174 | printf("net_io: using proxy %s:%d\n", proxy->host, proxy->port); |
175 | |
176 | curl_easy_setopt(curl, CURLOPT_PROXY, proxy->host); |
177 | curl_easy_setopt(curl, CURLOPT_PROXYPORT, proxy->port); |
178 | |
179 | if(proxy->use_authentication) { |
180 | printf("net_io: use auth for user %s\n", proxy->authentication_user); |
181 | |
182 | char *cred = g_strdup_printf("%s:%s", |
183 | proxy->authentication_user, |
184 | proxy->authentication_password); |
185 | |
186 | curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, cred); |
187 | g_free(cred); |
188 | } |
189 | } |
190 | } |
191 | |
192 | static void *worker_thread(void *ptr) { |
193 | net_io_request_t *request = (net_io_request_t*)ptr; |
194 | |
195 | printf("thread: running\n"); |
196 | |
197 | CURL *curl = curl_easy_init(); |
198 | if(curl) { |
199 | FILE *outfile = NULL; |
200 | gboolean ok = FALSE; |
201 | |
202 | /* prepare target (file, memory, ...) */ |
203 | switch(request->type) { |
204 | case NET_IO_DL_FILE: |
205 | outfile = fopen(request->filename, "w"); |
206 | ok = (outfile != NULL); |
207 | break; |
208 | |
209 | case NET_IO_DL_MEM: |
210 | request->mem.ptr = NULL; |
211 | request->mem.len = 0; |
212 | ok = TRUE; |
213 | break; |
214 | |
215 | default: |
216 | ok = TRUE; |
217 | break; |
218 | } |
219 | |
220 | if(ok) { |
221 | curl_easy_setopt(curl, CURLOPT_URL, request->url); |
222 | |
223 | switch(request->type) { |
224 | case NET_IO_DL_FILE: |
225 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, outfile); |
226 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); |
227 | break; |
228 | |
229 | case NET_IO_DL_MEM: |
230 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &request->mem); |
231 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, mem_write); |
232 | break; |
233 | |
234 | case NET_IO_DELETE: |
235 | curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); |
236 | break; |
237 | } |
238 | |
239 | net_io_set_proxy(curl, request->proxy); |
240 | |
241 | /* set user name and password for the authentication */ |
242 | if(request->user) |
243 | curl_easy_setopt(curl, CURLOPT_USERPWD, request->user); |
244 | |
245 | /* setup progress notification */ |
246 | curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); |
247 | curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, curl_progress_func); |
248 | curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, request); |
249 | |
250 | curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, request->buffer); |
251 | |
252 | /* play nice and report some user agent */ |
253 | curl_easy_setopt(curl, CURLOPT_USERAGENT, PACKAGE "-libcurl/" VERSION); |
254 | |
255 | request->res = curl_easy_perform(curl); |
256 | printf("thread: curl perform returned with %d\n", request->res); |
257 | |
258 | curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &request->response); |
259 | |
260 | if(request->type == NET_IO_DL_FILE) |
261 | fclose(outfile); |
262 | } |
263 | |
264 | /* always cleanup */ |
265 | curl_easy_cleanup(curl); |
266 | } |
267 | |
268 | printf("thread: io done\n"); |
269 | request_free(request); |
270 | |
271 | printf("thread: terminating\n"); |
272 | return NULL; |
273 | } |
274 | |
275 | static gboolean net_io_do(GtkWidget *parent, net_io_request_t *request) { |
276 | /* the request structure is shared between master and worker thread. */ |
277 | /* typically the master thread will do some waiting until the worker */ |
278 | /* thread returns. But the master may very well stop waiting since e.g. */ |
279 | /* the user activated some cancel button. The client will learn this */ |
280 | /* from the fact that it's holding the only reference to the request */ |
281 | |
282 | GtkWidget *pbar = NULL; |
283 | GtkWidget *dialog = busy_dialog(parent, &pbar, &request->cancel); |
284 | |
285 | /* create worker thread */ |
286 | request->refcount = 2; // master and worker hold a reference |
287 | if(!g_thread_create(&worker_thread, request, FALSE, NULL) != 0) { |
288 | g_warning("failed to create the worker thread"); |
289 | |
290 | /* free request and return error */ |
291 | request->refcount--; /* decrease by one for dead worker thread */ |
292 | gtk_widget_destroy(dialog); |
293 | return FALSE; |
294 | } |
295 | |
296 | /* wait for worker thread */ |
297 | float progress = 0; |
298 | while(request->refcount > 1 && !request->cancel) { |
299 | while(gtk_events_pending()) |
300 | gtk_main_iteration(); |
301 | |
302 | /* worker has made progress changed the progress value */ |
303 | if(request->progress != progress) { |
304 | gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(pbar), request->progress); |
305 | progress = request->progress; |
306 | } else |
307 | if(!progress) |
308 | gtk_progress_bar_pulse(GTK_PROGRESS_BAR(pbar)); |
309 | |
310 | usleep(100000); |
311 | } |
312 | |
313 | gtk_widget_destroy(dialog); |
314 | |
315 | /* user pressed cancel */ |
316 | if(request->refcount > 1) { |
317 | printf("operation cancelled, leave worker alone\n"); |
318 | return FALSE; |
319 | } |
320 | |
321 | printf("worker thread has ended\n"); |
322 | |
323 | /* --------- evaluate result --------- */ |
324 | |
325 | /* the http connection itself may have failed */ |
326 | if(request->res != 0) { |
327 | errorf(parent, _("Download failed with message:\n\n%s"), request->buffer); |
328 | return FALSE; |
329 | } |
330 | |
331 | /* a valid http connection may have returned an error */ |
332 | if(request->response != 200) { |
333 | errorf(parent, _("Download failed with code %ld:\n\n%s\n"), |
334 | request->response, http_message(request->response)); |
335 | return FALSE; |
336 | } |
337 | |
338 | return TRUE; |
339 | } |
340 | |
341 | gboolean net_io_download_file(GtkWidget *parent, settings_t *settings, |
342 | char *url, char *filename) { |
343 | net_io_request_t *request = g_new0(net_io_request_t, 1); |
344 | |
345 | printf("net_io: download %s to file %s\n", url, filename); |
346 | request->type = NET_IO_DL_FILE; |
347 | request->url = g_strdup(url); |
348 | request->filename = g_strdup(filename); |
349 | if(settings && settings->proxy) |
350 | request->proxy = settings->proxy; |
351 | |
352 | gboolean result = net_io_do(parent, request); |
353 | if(!result) { |
354 | |
355 | /* remove the file that may have been written by now. the kernel */ |
356 | /* should cope with the fact that the worker thread may still have */ |
357 | /* an open reference to this and might thus still write to this file. */ |
358 | /* letting the worker delete the file is worse since it may take the */ |
359 | /* worker some time to come to the point to delete this file. If the */ |
360 | /* user has restarted the download by then, the worker will erase that */ |
361 | /* newly written file */ |
362 | |
363 | g_remove(filename); |
364 | } |
365 | |
366 | request_free(request); |
367 | return result; |
368 | } |
369 | |
370 | |
371 | gboolean net_io_download_mem(GtkWidget *parent, settings_t *settings, |
372 | char *url, char **mem) { |
373 | net_io_request_t *request = g_new0(net_io_request_t, 1); |
374 | |
375 | printf("net_io: download %s to memory\n", url); |
376 | request->type = NET_IO_DL_MEM; |
377 | request->url = g_strdup(url); |
378 | if(settings && settings->proxy) |
379 | request->proxy = settings->proxy; |
380 | |
381 | gboolean result = net_io_do(parent, request); |
382 | if(result) { |
383 | printf("ptr = %p, len = %d\n", request->mem.ptr, request->mem.len); |
384 | *mem = request->mem.ptr; |
385 | } |
386 | |
387 | request_free(request); |
388 | return result; |
389 | } |