Parent Directory | Revision Log
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 | } |