FIX: lutil_quvi_init: Pass handle value to quvi_errmsg
[quvi-tool.git] / src / get / http.c
blobddf301396ef8764a4c8a832b6f09daf4701ab408
1 /* quvi
2 * Copyright (C) 2012,2013 Toni Gundogdu <legatvs@gmail.com>
4 * This file is part of quvi <http://quvi.sourceforge.net/>.
6 * This program is free software: you can redistribute it and/or
7 * modify it under the terms of the GNU Affero General Public
8 * License as published by the Free Software Foundation, either
9 * version 3 of the License, or (at your option) any later version.
11 * This program 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 Affero General Public License for more details.
16 * You should have received a copy of the GNU Affero General
17 * Public License along with this program. If not, see
18 * <http://www.gnu.org/licenses/>.
21 #include "config.h"
23 #include <stdlib.h>
24 #include <glib/gi18n.h>
25 #include <quvi.h>
26 #include <curl/curl.h>
28 #include "lutil.h"
29 #include "lpbar.h"
30 #include "lget.h"
32 static gboolean force_skip_transfer = FALSE;
33 static gboolean transfer_skipped = FALSE;
34 static quvi_http_metainfo_t qmi = NULL;
35 static struct lutil_file_open_s fo;
36 static gdouble content_length = 0;
37 static gchar *content_type = NULL;
38 static gchar *io_errmsg = NULL;
39 static CURLcode curl_code = 0;
40 static lpbar_t pbar = NULL;
41 static lget_t g = NULL;
42 static CURL *c = NULL;
44 static gdouble _content_length_from_qmi()
46 gdouble l = 0;
47 quvi_http_metainfo_get(qmi, QUVI_HTTP_METAINFO_PROPERTY_LENGTH_BYTES, &l);
48 return (l);
51 static gchar *_content_type_from_qmi()
53 gchar *s = NULL;
54 quvi_http_metainfo_get(qmi, QUVI_HTTP_METAINFO_PROPERTY_CONTENT_TYPE, &s);
55 return (g_strdup(s));
58 static gdouble _content_length_from_c()
60 gdouble l = 0;
61 curl_easy_getinfo(c, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &l);
62 return (l);
65 static gchar *_content_type_from_c()
67 gchar *s = NULL;
68 curl_easy_getinfo(c, CURLINFO_CONTENT_TYPE, &s);
69 return (g_strdup(s));
72 typedef enum {HTTP_HEAD_RESPONSE, HTTP_GET_RESPONSE} HttpResponseType;
74 static void _content_props_from(const HttpResponseType hrt)
76 g_free(content_type);
77 if (hrt == HTTP_HEAD_RESPONSE)
79 content_length = _content_length_from_qmi();
80 content_type = _content_type_from_qmi();
82 else
84 content_length = _content_length_from_c();
85 content_type = _content_type_from_c();
87 pbar->content_type = g_strdup(content_type);
88 pbar->content_bytes = content_length;
91 static gint _cleanup(const gint r)
93 quvi_http_metainfo_free(qmi);
94 qmi = NULL;
96 g_free(content_type);
97 content_type = NULL;
99 lpbar_free(pbar);
100 pbar = NULL;
102 if (fo.result.file != NULL)
104 fflush(fo.result.file);
105 fclose(fo.result.file);
106 fo.result.file = NULL;
108 c = NULL;
110 return (r);
113 static gint _print_unexpected_errmsg(const glong rc, const glong cc)
115 g_printerr(_("\nerror: Server responded with code %ld, "
116 "expected 200 or 206 (cc=%ld)\n"), rc, cc);
117 return (EXIT_FAILURE);
120 static gint _print_curl_errmsg(const glong rc, const glong cc)
122 g_printerr(_("\nerror: libcurl: %s (curl_code=%u, rc=%lu, cc=%lu)\n"),
123 curl_easy_strerror(curl_code), curl_code, rc, cc);
124 return (EXIT_FAILURE);
127 static gint _print_io_errmsg()
129 if (io_errmsg != NULL)
131 g_printerr(_("\nerror: while writing to file: %s\n"), io_errmsg);
132 g_free(io_errmsg);
134 return (EXIT_FAILURE);
137 static gint _chk_transfer_errors(CURL *c)
139 glong rc, cc;
140 gint r;
142 r = EXIT_SUCCESS;
143 rc = 0;
144 cc = 0;
146 curl_easy_getinfo(c, CURLINFO_HTTP_CONNECTCODE, &cc);
147 curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &rc);
149 if (curl_code == CURLE_OK)
151 if (rc != 200 && rc != 206)
152 r = _print_unexpected_errmsg(rc, cc);
154 else
156 if (curl_code != CURLE_WRITE_ERROR)
157 r = _print_curl_errmsg(rc, cc);
158 else /* _write_cb returned error (0) */
160 r = EXIT_FAILURE;
161 if (fo.result.skip_retrieved_already == FALSE
162 && force_skip_transfer == FALSE)
164 _print_io_errmsg();
168 return (r);
171 static gint _set_io_errmsg()
173 io_errmsg = lutil_strerror();
174 return (0);
177 static gint _build_fpath()
179 lutil_build_fpath_t b;
180 quvi_file_ext_t qfe;
182 if (g->result.fpath != NULL) /* Skip re-building. */
183 return (EXIT_SUCCESS);
185 b = g->build_fpath;
186 qfe = quvi_file_ext_new(g->q, content_type);
188 if (quvi_ok(g->q) == FALSE)
190 g->xperr(_("libquvi: while parsing file extension: %s"),
191 quvi_errmsg(g->q));
192 quvi_file_ext_free(qfe);
193 return (EXIT_FAILURE);
196 b->file_ext = quvi_file_ext_get(qfe);
197 g->result.fpath = lutil_build_fpath(b);
199 quvi_file_ext_free(qfe);
201 if (g->result.fpath != NULL)
203 pbar->fname = g_path_get_basename(g->result.fpath);
204 return (EXIT_SUCCESS);
206 return (EXIT_FAILURE);
209 static gint _open_file()
211 if (_build_fpath() != EXIT_SUCCESS)
212 return (EXIT_FAILURE);
214 lpbar_print(pbar, force_skip_transfer);
216 if (force_skip_transfer == TRUE)
217 return (EXIT_FAILURE);
220 * Force overwrite if resume-from<0 was given.
222 * libcurl requires setting CURLOPT_RESUME_FROM_LARGE before
223 * `curl_easy_perform' is called. The the program has no way of
224 * knowing whether the transfer should be resumed if `content-length'
225 * is not queried by sending a HTTP HEAD request before the transfer
226 * begins.
229 if (g->opts.resume_from >= 0)
230 fo.overwrite_if_exists = g->opts.overwrite_if_exists;
231 else
232 fo.overwrite_if_exists = TRUE;
234 if (content_length >0)
235 fo.content_bytes = content_length;
237 fo.fpath = g->result.fpath;
238 fo.xperr = g->xperr;
240 return (lutil_file_open(&fo));
243 static gint _chk_skipped()
245 pbar->flags.failed = TRUE; /* Do not print update line. */
246 transfer_skipped = TRUE;
248 if (fo.result.skip_retrieved_already == TRUE)
249 g_printerr(_("skip: stream retrieved completely already\n"));
250 else if (force_skip_transfer == TRUE)
251 g_printerr(_("skip: requested\n"));
252 else
253 transfer_skipped = FALSE;
255 return (EXIT_FAILURE);
258 static gint _chk_file_open()
260 if (fo.result.file != NULL)
261 return (EXIT_SUCCESS);
263 _content_props_from(HTTP_GET_RESPONSE);
265 if (_open_file() != EXIT_SUCCESS)
266 return (_chk_skipped());
268 return (EXIT_SUCCESS);
271 static gsize _write_cb(gpointer data, gsize size, gsize nmemb, gpointer udata)
273 if (_chk_file_open() != EXIT_SUCCESS)
274 return (0);
276 if (fwrite(data, size, nmemb, fo.result.file) != nmemb)
277 return (_set_io_errmsg());
279 if (fflush(fo.result.file) != 0)
280 return (_set_io_errmsg());
282 return (size*nmemb);
285 static gint _progress_cb(gpointer clientp, gdouble dltotal, gdouble dlnow,
286 gdouble ultotal, gdouble ulnow)
288 return (lpbar_update((lpbar_t) clientp, dlnow));
291 static gint _chk_autoresume()
294 * Force HTTP HEAD check with resume-from=0 (autoresume).
296 * Retrieve the content-{length,type} from the returned response,
297 * build the file path, and open the file.
299 * NOTE: file path (or name) cannot be generated unless content-type
300 * has been retrieved, the value is used to create the file
301 * extension which is replaced by the xchg subsystem of lutil.
303 * Otherwise, proceed to open the stream, and do all of the above in
304 * the write-callback, instead. This has the advantage of skipping the
305 * HTTP HEAD request, and the disadvantage of not knowing whether the
306 * transfer should be resumed, see the CURLOPT_RESUME_FROM_LARGE notes
307 * above.
310 lutil_build_fpath_t b = g->build_fpath;
312 if (lutil_query_metainfo(g->q, b->qm, &qmi, b->xperr) != EXIT_SUCCESS)
313 return (EXIT_FAILURE);
315 _content_props_from(HTTP_HEAD_RESPONSE);
317 if (_open_file() != EXIT_SUCCESS)
318 return (_chk_skipped());
320 return (EXIT_SUCCESS);
323 static gint _setup_curl()
325 if (g->opts.resume_from >= 0) /* 0=auto, >0 from the specified offset. */
327 gdouble o = g->opts.resume_from;
328 if (g->opts.resume_from ==0)
330 if (_chk_autoresume() != EXIT_SUCCESS)
331 return (EXIT_FAILURE);
332 o = fo.result.initial_bytes;
334 curl_easy_setopt(c, CURLOPT_RESUME_FROM_LARGE, (curl_off_t) o);
335 pbar->initial_bytes = o;
339 * Set the curl handle options _after_ metainfo has been retrieved.
340 * Otherwise these settings would interfere with it.
342 curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, _write_cb);
343 curl_easy_setopt(c, CURLOPT_URL, g->url);
345 curl_easy_setopt(c, CURLOPT_ENCODING, "identity");
346 curl_easy_setopt(c, CURLOPT_HEADER, 0L);
348 curl_easy_setopt(c, CURLOPT_PROGRESSFUNCTION, _progress_cb);
349 curl_easy_setopt(c, CURLOPT_PROGRESSDATA, pbar);
350 curl_easy_setopt(c, CURLOPT_NOPROGRESS, 0L);
352 return (EXIT_SUCCESS);
355 static void _reset_curl()
357 curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, NULL);
358 curl_easy_setopt(c, CURLOPT_ENCODING, "");
360 curl_easy_setopt(c, CURLOPT_PROGRESSFUNCTION, NULL);
361 curl_easy_setopt(c, CURLOPT_PROGRESSDATA, NULL);
362 curl_easy_setopt(c, CURLOPT_NOPROGRESS, 1L);
364 curl_easy_setopt(c, CURLOPT_RESUME_FROM_LARGE, 0L);
367 static gint _open_stream()
369 gint r;
371 c = lutil_curl_handle_from(g->q);
372 if (c == NULL)
374 g->xperr(_("error: failed to retrieve the current curl "
375 "session handle from libquvi"));
376 return (EXIT_FAILURE);
379 pbar = lpbar_new();
380 r = _setup_curl();
382 if (r == EXIT_SUCCESS)
384 curl_code = curl_easy_perform(c);
385 r = _chk_transfer_errors(c);
386 _reset_curl();
389 pbar->flags.failed = (r != EXIT_SUCCESS) ? TRUE:FALSE;
390 return (r);
393 static gint _exec_cmd()
395 struct lutil_exec_opts_s xopts;
396 gint i, r;
398 if (g->opts.exec.external == NULL)
399 return (EXIT_SUCCESS);
401 for (i=0, r=EXIT_SUCCESS;
402 g->opts.exec.external[i] != NULL && r == EXIT_SUCCESS;
403 ++i)
405 memset(&xopts, 0, sizeof(struct lutil_exec_opts_s));
407 xopts.flags.discard_stderr = !g->opts.exec.enable_stderr;
408 xopts.flags.discard_stdout = !g->opts.exec.enable_stdout;
409 xopts.flags.dump_argv = g->opts.exec.dump_argv;
411 xopts.exec_arg = g->opts.exec.external[i];
412 xopts.xperr = g->xperr;
414 xopts.fpath = g->result.fpath;
415 xopts.qm = g->build_fpath->qm;
417 r = lutil_exec_cmd(&xopts);
419 return (r);
422 gint lget_http_get(lget_t handle)
424 gint r;
426 memset(&fo, 0, sizeof(struct lutil_file_open_s));
428 g = handle;
429 qmi = NULL;
431 content_length = 0;
432 content_type = NULL;
434 io_errmsg = NULL;
435 pbar = NULL;
437 curl_code = 0;
438 c = NULL;
440 force_skip_transfer = g->opts.skip_transfer;
441 transfer_skipped = FALSE;
444 * If file was retrieved completely already, lutil_open_file set
445 * 'skip_retrieved_already' flag, and return EXIT_FAILURE.
447 r = _open_stream();
448 if (r == EXIT_SUCCESS || fo.result.skip_retrieved_already == TRUE)
449 r = _exec_cmd();
451 /* If --skip-transfer was defined, the */
452 if (transfer_skipped == TRUE)
453 r = EXIT_SUCCESS;
455 return (_cleanup(r));
458 /* vim: set ts=2 sw=2 tw=72 expandtab: */