Contents of /trunk/src/net_io.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 4 - (show annotations)
Wed Dec 10 19:50:17 2008 UTC (15 years, 4 months ago) by harbaum
File MIME type: text/plain
File size: 9561 byte(s)
Asynchronous network io
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_FILE, NET_IO_MEM } net_io_type_t;
51
52 typedef struct {
53 net_io_type_t type;
54 gint refcount; /* reference counter for master and worker thread */
55
56 char *url;
57 gboolean cancel;
58 float progress;
59
60 /* curl/http related stuff: */
61 CURLcode res;
62 long response;
63 char buffer[CURL_ERROR_SIZE];
64
65 union {
66 char *filename;
67 curl_mem_t mem;
68 };
69
70 } net_io_request_t;
71
72 static char *http_message(int id) {
73 struct http_message_s *msg = http_messages;
74
75 while(msg->id) {
76 if(msg->id == id) return _(msg->msg);
77 msg++;
78 }
79
80 return NULL;
81 }
82
83 static gint dialog_destroy_event(GtkWidget *widget, gpointer data) {
84 /* set cancel flag */
85 *(gboolean*)data = TRUE;
86 return FALSE;
87 }
88
89 static void on_cancel(GtkWidget *widget, gpointer data) {
90 /* set cancel flag */
91 *(gboolean*)data = TRUE;
92 }
93
94 /* create the dialog box shown while worker is running */
95 static GtkWidget *busy_dialog(GtkWidget *parent, GtkWidget **pbar,
96 gboolean *cancel_ind) {
97 GtkWidget *dialog = gtk_dialog_new();
98
99 gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
100 gtk_window_set_title(GTK_WINDOW(dialog), _("Downloading..."));
101 gtk_window_set_default_size(GTK_WINDOW(dialog), 300, 10);
102
103 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
104 gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent));
105
106 g_assert(pbar);
107 *pbar = gtk_progress_bar_new();
108 gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(*pbar), 0.1);
109
110 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), *pbar);
111
112 GtkWidget *button = gtk_button_new_with_label(_("Cancel"));
113 gtk_signal_connect(GTK_OBJECT(button), "clicked",
114 GTK_SIGNAL_FUNC(on_cancel), (gpointer)cancel_ind);
115 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), button);
116
117 gtk_signal_connect(GTK_OBJECT(dialog), "destroy",
118 G_CALLBACK(dialog_destroy_event), (gpointer)cancel_ind);
119
120 gtk_widget_show_all(dialog);
121
122 return dialog;
123 }
124
125 static void request_free(net_io_request_t *request) {
126 g_free(request->url);
127 if(request->type == NET_IO_FILE)
128 g_free(request->filename);
129
130 g_free(request);
131 }
132
133 static int curl_progress_func(net_io_request_t *request,
134 double t, /* dltotal */ double d, /* dlnow */
135 double ultotal, double ulnow) {
136 request->progress = t?d/t:0;
137 return 0;
138 }
139
140 static size_t mem_write(void *ptr, size_t size, size_t nmemb,
141 void *stream) {
142 curl_mem_t *p = (curl_mem_t*)stream;
143
144 p->ptr = g_realloc(p->ptr, p->len + size*nmemb + 1);
145 if(p->ptr) {
146 memcpy(p->ptr+p->len, ptr, size*nmemb);
147 p->len += size*nmemb;
148 p->ptr[p->len] = 0;
149 }
150 return nmemb;
151 }
152
153 static void *worker_thread(void *ptr) {
154 net_io_request_t *request = (net_io_request_t*)ptr;
155
156 printf("thread: running\n");
157
158 CURL *curl = curl_easy_init();
159 if(curl) {
160 FILE *outfile = NULL;
161 gboolean ok = FALSE;
162
163 if(request->type == NET_IO_FILE) {
164 outfile = fopen(request->filename, "w");
165 ok = (outfile != NULL);
166 } else {
167 request->mem.ptr = NULL;
168 request->mem.len = 0;
169 ok = TRUE;
170 }
171
172 if(ok) {
173 curl_easy_setopt(curl, CURLOPT_URL, request->url);
174 if(request->type == NET_IO_FILE) {
175 curl_easy_setopt(curl, CURLOPT_WRITEDATA, outfile);
176 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
177 } else {
178 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &request->mem);
179 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, mem_write);
180 }
181 curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
182 curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, curl_progress_func);
183 curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, request);
184 curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, request->buffer);
185
186 request->res = curl_easy_perform(curl);
187 printf("thread: curl perform returned with %d\n", request->res);
188
189 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &request->response);
190
191 if(request->type == NET_IO_FILE)
192 fclose(outfile);
193 }
194
195 /* always cleanup */
196 curl_easy_cleanup(curl);
197 }
198
199 printf("thread: io done\n");
200
201 if(request->refcount < 2) {
202 printf("thread: master has prematurely released request\n");
203
204 request_free(request);
205 } else {
206 printf("thread: informing master about result\n");
207
208 request->refcount--;
209 }
210
211 printf("thread: terminating\n");
212 return NULL;
213 }
214
215 static gboolean net_io_do(GtkWidget *parent, net_io_request_t *request) {
216 GtkWidget *pbar = NULL;
217 GtkWidget *dialog = busy_dialog(parent, &pbar, &request->cancel);
218
219 /* create worker thread */
220 request->refcount = 2; // master and worker hold a reference
221 if(!g_thread_create(&worker_thread, request, FALSE, NULL) != 0) {
222 g_warning("failed to create the worker thread");
223
224 /* free request and return error */
225 request_free(request);
226 gtk_widget_destroy(dialog);
227 return FALSE;
228 }
229
230 /* wait for worker thread */
231 float progress = 0;
232 while(request->refcount > 1 && !request->cancel) {
233 while(gtk_events_pending())
234 gtk_main_iteration();
235
236 /* worker has made progress changed the progress value */
237 if(request->progress != progress) {
238 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(pbar), request->progress);
239 progress = request->progress;
240 } else
241 if(!progress)
242 gtk_progress_bar_pulse(GTK_PROGRESS_BAR(pbar));
243
244 usleep(10000);
245 }
246
247 gtk_widget_destroy(dialog);
248
249 /* user pressed cancel */
250 if(request->refcount > 1) {
251 printf("operation cancelled, leave worker alone\n");
252
253 /* remove the file that may have been written by now. the kernel */
254 /* should cope with the fact that the worker thread may still have */
255 /* an open reference to this and might thus still write to this file. */
256 /* letting the worker delete the file is worse since it may take the */
257 /* worker some time to come to the point to delete this file. If the */
258 /* user has restarted the download by then, the worker will erase that */
259 /* newly written file */
260 g_remove(request->filename);
261
262 request->refcount--;
263 return FALSE;
264 }
265
266 printf("worker thread has ended\n");
267
268 /* --------- evaluate result --------- */
269
270 if(request->res != 0) {
271 errorf(parent, _("Download failed with message:\n\n%s"), request->buffer);
272 g_remove(request->filename);
273 request_free(request);
274 return FALSE;
275 }
276
277 if(request->response != 200) {
278 errorf(parent, _("Download failed with code %ld:\n\n%s\n"),
279 request->response, http_message(request->response));
280 g_remove(request->filename);
281 request_free(request);
282 return FALSE;
283 }
284
285 /* memory transfer needs the result as it contains the result pointer */
286 if(request->type != NET_IO_MEM)
287 request_free(request);
288
289 return TRUE;
290 }
291
292 gboolean net_io_download_file(GtkWidget *parent, char *url, char *filename) {
293 /* the request structure is shared between master and worker thread. */
294 /* typically the master thread will do some waiting until the worker */
295 /* thread returns. But the master may very well stop waiting since e.g. */
296 /* the user activated some cancel button. The client will learn this */
297 /* from the fact that it's holding the only reference to the request */
298 net_io_request_t *request = g_new0(net_io_request_t, 1);
299
300 printf("net_io: download %s to file %s\n", url, filename);
301 request->type = NET_IO_FILE;
302 request->url = g_strdup(url);
303 request->filename = g_strdup(filename);
304
305 return net_io_do(parent, request);
306 }
307
308
309 void *net_io_download_mem(GtkWidget *parent, char *url) {
310 /* the request structure is shared between master and worker thread. */
311 /* typically the master thread will do some waiting until the worker */
312 /* thread returns. But the master may very well stop waiting since e.g. */
313 /* the user activated some cancel button. The client will learn this */
314 /* from the fact that it's holding the only reference to the request */
315 net_io_request_t *request = g_new0(net_io_request_t, 1);
316
317 printf("net_io: download %s to memory\n", url);
318 request->type = NET_IO_MEM;
319 request->url = g_strdup(url);
320
321 if(!net_io_do(parent, request))
322 return NULL;
323
324 printf("ptr = %p, len = %d\n",
325 request->mem.ptr, request->mem.len);
326
327 char *retval = request->mem.ptr;
328 request_free(request);
329 return retval;
330 }