Contents of /trunk/src/net_io.c

Parent Directory Parent Directory | Revision Log Revision Log


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