Contents of /trunk/src/net_io.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 4 - (hide annotations)
Wed Dec 10 19:50:17 2008 UTC (15 years, 5 months ago) by harbaum
File MIME type: text/plain
File size: 9561 byte(s)
Asynchronous network io
1 harbaum 4 /*
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     }