Contents of /trunk/src/net_io.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 177 - (show annotations)
Wed Jun 10 12:07:11 2009 UTC (15 years ago) by harbaum
File MIME type: text/plain
File size: 11150 byte(s)
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 }