47 |
int len; |
int len; |
48 |
} curl_mem_t; |
} curl_mem_t; |
49 |
|
|
50 |
typedef enum { NET_IO_FILE, NET_IO_MEM } net_io_type_t; |
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 { |
typedef struct { |
54 |
net_io_type_t type; |
net_io_type_t type; |
55 |
gint refcount; /* reference counter for master and worker thread */ |
gint refcount; /* reference counter for master and worker thread */ |
56 |
|
|
57 |
char *url; |
char *url, *user; |
58 |
gboolean cancel; |
gboolean cancel; |
59 |
float progress; |
float progress; |
60 |
|
|
63 |
long response; |
long response; |
64 |
char buffer[CURL_ERROR_SIZE]; |
char buffer[CURL_ERROR_SIZE]; |
65 |
|
|
66 |
|
/* request specific fields */ |
67 |
union { |
union { |
68 |
char *filename; |
char *filename; /* used for NET_IO_DL_FILE */ |
69 |
curl_mem_t mem; |
curl_mem_t mem; /* used for NET_IO_DL_MEM */ |
70 |
}; |
}; |
71 |
|
|
72 |
} net_io_request_t; |
} net_io_request_t; |
125 |
} |
} |
126 |
|
|
127 |
static void request_free(net_io_request_t *request) { |
static void request_free(net_io_request_t *request) { |
128 |
g_free(request->url); |
/* decrease refcount and only free structure if no references are left */ |
129 |
if(request->type == NET_IO_FILE) |
request->refcount--; |
130 |
|
if(request->refcount) { |
131 |
|
printf("still %d references, keeping request\n", request->refcount); |
132 |
|
return; |
133 |
|
} |
134 |
|
|
135 |
|
printf("no references left, freeing request\n"); |
136 |
|
if(request->url) g_free(request->url); |
137 |
|
if(request->user) g_free(request->user); |
138 |
|
|
139 |
|
/* filename is only a valid filename in NET_IO_DL_FILE mode */ |
140 |
|
if(request->type == NET_IO_DL_FILE && request->filename) |
141 |
g_free(request->filename); |
g_free(request->filename); |
142 |
|
|
143 |
g_free(request); |
g_free(request); |
173 |
FILE *outfile = NULL; |
FILE *outfile = NULL; |
174 |
gboolean ok = FALSE; |
gboolean ok = FALSE; |
175 |
|
|
176 |
if(request->type == NET_IO_FILE) { |
/* prepare target (file, memory, ...) */ |
177 |
|
switch(request->type) { |
178 |
|
case NET_IO_DL_FILE: |
179 |
outfile = fopen(request->filename, "w"); |
outfile = fopen(request->filename, "w"); |
180 |
ok = (outfile != NULL); |
ok = (outfile != NULL); |
181 |
} else { |
break; |
182 |
|
|
183 |
|
case NET_IO_DL_MEM: |
184 |
request->mem.ptr = NULL; |
request->mem.ptr = NULL; |
185 |
request->mem.len = 0; |
request->mem.len = 0; |
186 |
ok = TRUE; |
ok = TRUE; |
187 |
|
break; |
188 |
|
|
189 |
|
default: |
190 |
|
ok = TRUE; |
191 |
|
break; |
192 |
} |
} |
193 |
|
|
194 |
if(ok) { |
if(ok) { |
195 |
curl_easy_setopt(curl, CURLOPT_URL, request->url); |
curl_easy_setopt(curl, CURLOPT_URL, request->url); |
196 |
if(request->type == NET_IO_FILE) { |
|
197 |
|
switch(request->type) { |
198 |
|
case NET_IO_DL_FILE: |
199 |
curl_easy_setopt(curl, CURLOPT_WRITEDATA, outfile); |
curl_easy_setopt(curl, CURLOPT_WRITEDATA, outfile); |
200 |
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); |
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); |
201 |
} else { |
break; |
202 |
|
|
203 |
|
case NET_IO_DL_MEM: |
204 |
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &request->mem); |
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &request->mem); |
205 |
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, mem_write); |
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, mem_write); |
206 |
|
break; |
207 |
|
|
208 |
|
case NET_IO_DELETE: |
209 |
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); |
210 |
|
break; |
211 |
} |
} |
212 |
|
|
213 |
|
/* set user name and password for the authentication */ |
214 |
|
if(request->user) |
215 |
|
curl_easy_setopt(curl, CURLOPT_USERPWD, request->user); |
216 |
|
|
217 |
|
/* setup progress notification */ |
218 |
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); |
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); |
219 |
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, curl_progress_func); |
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, curl_progress_func); |
220 |
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, request); |
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, request); |
221 |
|
|
222 |
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, request->buffer); |
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, request->buffer); |
223 |
|
|
224 |
|
/* play nice and report some user agent */ |
225 |
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, PACKAGE "-libcurl/" VERSION); |
226 |
|
|
227 |
request->res = curl_easy_perform(curl); |
request->res = curl_easy_perform(curl); |
228 |
printf("thread: curl perform returned with %d\n", request->res); |
printf("thread: curl perform returned with %d\n", request->res); |
229 |
|
|
230 |
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &request->response); |
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &request->response); |
231 |
|
|
232 |
if(request->type == NET_IO_FILE) |
if(request->type == NET_IO_DL_FILE) |
233 |
fclose(outfile); |
fclose(outfile); |
234 |
} |
} |
235 |
|
|
238 |
} |
} |
239 |
|
|
240 |
printf("thread: io done\n"); |
printf("thread: io done\n"); |
241 |
|
request_free(request); |
|
if(request->refcount < 2) { |
|
|
printf("thread: master has prematurely released request\n"); |
|
|
|
|
|
request_free(request); |
|
|
} else { |
|
|
printf("thread: informing master about result\n"); |
|
|
|
|
|
request->refcount--; |
|
|
} |
|
242 |
|
|
243 |
printf("thread: terminating\n"); |
printf("thread: terminating\n"); |
244 |
return NULL; |
return NULL; |
245 |
} |
} |
246 |
|
|
247 |
static gboolean net_io_do(GtkWidget *parent, net_io_request_t *request) { |
static gboolean net_io_do(GtkWidget *parent, net_io_request_t *request) { |
248 |
|
/* the request structure is shared between master and worker thread. */ |
249 |
|
/* typically the master thread will do some waiting until the worker */ |
250 |
|
/* thread returns. But the master may very well stop waiting since e.g. */ |
251 |
|
/* the user activated some cancel button. The client will learn this */ |
252 |
|
/* from the fact that it's holding the only reference to the request */ |
253 |
|
|
254 |
GtkWidget *pbar = NULL; |
GtkWidget *pbar = NULL; |
255 |
GtkWidget *dialog = busy_dialog(parent, &pbar, &request->cancel); |
GtkWidget *dialog = busy_dialog(parent, &pbar, &request->cancel); |
256 |
|
|
260 |
g_warning("failed to create the worker thread"); |
g_warning("failed to create the worker thread"); |
261 |
|
|
262 |
/* free request and return error */ |
/* free request and return error */ |
263 |
request_free(request); |
request->refcount--; /* decrease by one for dead worker thread */ |
264 |
gtk_widget_destroy(dialog); |
gtk_widget_destroy(dialog); |
265 |
return FALSE; |
return FALSE; |
266 |
} |
} |
279 |
if(!progress) |
if(!progress) |
280 |
gtk_progress_bar_pulse(GTK_PROGRESS_BAR(pbar)); |
gtk_progress_bar_pulse(GTK_PROGRESS_BAR(pbar)); |
281 |
|
|
282 |
usleep(10000); |
usleep(100000); |
283 |
} |
} |
284 |
|
|
285 |
gtk_widget_destroy(dialog); |
gtk_widget_destroy(dialog); |
287 |
/* user pressed cancel */ |
/* user pressed cancel */ |
288 |
if(request->refcount > 1) { |
if(request->refcount > 1) { |
289 |
printf("operation cancelled, leave worker alone\n"); |
printf("operation cancelled, leave worker alone\n"); |
|
|
|
|
/* remove the file that may have been written by now. the kernel */ |
|
|
/* should cope with the fact that the worker thread may still have */ |
|
|
/* an open reference to this and might thus still write to this file. */ |
|
|
/* letting the worker delete the file is worse since it may take the */ |
|
|
/* worker some time to come to the point to delete this file. If the */ |
|
|
/* user has restarted the download by then, the worker will erase that */ |
|
|
/* newly written file */ |
|
|
g_remove(request->filename); |
|
|
|
|
|
request->refcount--; |
|
290 |
return FALSE; |
return FALSE; |
291 |
} |
} |
292 |
|
|
294 |
|
|
295 |
/* --------- evaluate result --------- */ |
/* --------- evaluate result --------- */ |
296 |
|
|
297 |
|
/* the http connection itself may have failed */ |
298 |
if(request->res != 0) { |
if(request->res != 0) { |
299 |
errorf(parent, _("Download failed with message:\n\n%s"), request->buffer); |
errorf(parent, _("Download failed with message:\n\n%s"), request->buffer); |
|
g_remove(request->filename); |
|
|
request_free(request); |
|
300 |
return FALSE; |
return FALSE; |
301 |
} |
} |
302 |
|
|
303 |
|
/* a valid http connection may have returned an error */ |
304 |
if(request->response != 200) { |
if(request->response != 200) { |
305 |
errorf(parent, _("Download failed with code %ld:\n\n%s\n"), |
errorf(parent, _("Download failed with code %ld:\n\n%s\n"), |
306 |
request->response, http_message(request->response)); |
request->response, http_message(request->response)); |
|
g_remove(request->filename); |
|
|
request_free(request); |
|
307 |
return FALSE; |
return FALSE; |
308 |
} |
} |
309 |
|
|
|
/* memory transfer needs the result as it contains the result pointer */ |
|
|
if(request->type != NET_IO_MEM) |
|
|
request_free(request); |
|
|
|
|
310 |
return TRUE; |
return TRUE; |
311 |
} |
} |
312 |
|
|
313 |
gboolean net_io_download_file(GtkWidget *parent, char *url, char *filename) { |
gboolean net_io_download_file(GtkWidget *parent, char *url, char *filename) { |
|
/* the request structure is shared between master and worker thread. */ |
|
|
/* typically the master thread will do some waiting until the worker */ |
|
|
/* thread returns. But the master may very well stop waiting since e.g. */ |
|
|
/* the user activated some cancel button. The client will learn this */ |
|
|
/* from the fact that it's holding the only reference to the request */ |
|
314 |
net_io_request_t *request = g_new0(net_io_request_t, 1); |
net_io_request_t *request = g_new0(net_io_request_t, 1); |
315 |
|
|
316 |
printf("net_io: download %s to file %s\n", url, filename); |
printf("net_io: download %s to file %s\n", url, filename); |
317 |
request->type = NET_IO_FILE; |
request->type = NET_IO_DL_FILE; |
318 |
request->url = g_strdup(url); |
request->url = g_strdup(url); |
319 |
request->filename = g_strdup(filename); |
request->filename = g_strdup(filename); |
320 |
|
|
321 |
return net_io_do(parent, request); |
gboolean result = net_io_do(parent, request); |
322 |
|
if(!result) { |
323 |
|
|
324 |
|
/* remove the file that may have been written by now. the kernel */ |
325 |
|
/* should cope with the fact that the worker thread may still have */ |
326 |
|
/* an open reference to this and might thus still write to this file. */ |
327 |
|
/* letting the worker delete the file is worse since it may take the */ |
328 |
|
/* worker some time to come to the point to delete this file. If the */ |
329 |
|
/* user has restarted the download by then, the worker will erase that */ |
330 |
|
/* newly written file */ |
331 |
|
|
332 |
|
g_remove(filename); |
333 |
|
} |
334 |
|
|
335 |
|
request_free(request); |
336 |
|
return result; |
337 |
} |
} |
338 |
|
|
339 |
|
|
340 |
void *net_io_download_mem(GtkWidget *parent, char *url) { |
gboolean net_io_download_mem(GtkWidget *parent, char *url, char **mem) { |
|
/* the request structure is shared between master and worker thread. */ |
|
|
/* typically the master thread will do some waiting until the worker */ |
|
|
/* thread returns. But the master may very well stop waiting since e.g. */ |
|
|
/* the user activated some cancel button. The client will learn this */ |
|
|
/* from the fact that it's holding the only reference to the request */ |
|
341 |
net_io_request_t *request = g_new0(net_io_request_t, 1); |
net_io_request_t *request = g_new0(net_io_request_t, 1); |
342 |
|
|
343 |
printf("net_io: download %s to memory\n", url); |
printf("net_io: download %s to memory\n", url); |
344 |
request->type = NET_IO_MEM; |
request->type = NET_IO_DL_MEM; |
345 |
request->url = g_strdup(url); |
request->url = g_strdup(url); |
346 |
|
|
347 |
if(!net_io_do(parent, request)) |
gboolean result = net_io_do(parent, request); |
348 |
return NULL; |
if(result) { |
349 |
|
printf("ptr = %p, len = %d\n", request->mem.ptr, request->mem.len); |
350 |
printf("ptr = %p, len = %d\n", |
*mem = request->mem.ptr; |
351 |
request->mem.ptr, request->mem.len); |
} |
352 |
|
|
|
char *retval = request->mem.ptr; |
|
353 |
request_free(request); |
request_free(request); |
354 |
return retval; |
return result; |
355 |
} |
} |