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/>.
24 #include <glib/gi18n.h>
26 #include <curl/curl.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()
47 quvi_http_metainfo_get(qmi
, QUVI_HTTP_METAINFO_PROPERTY_LENGTH_BYTES
, &l
);
51 static gchar
*_content_type_from_qmi()
54 quvi_http_metainfo_get(qmi
, QUVI_HTTP_METAINFO_PROPERTY_CONTENT_TYPE
, &s
);
58 static gdouble
_content_length_from_c()
61 curl_easy_getinfo(c
, CURLINFO_CONTENT_LENGTH_DOWNLOAD
, &l
);
65 static gchar
*_content_type_from_c()
68 curl_easy_getinfo(c
, CURLINFO_CONTENT_TYPE
, &s
);
72 typedef enum {HTTP_HEAD_RESPONSE
, HTTP_GET_RESPONSE
} HttpResponseType
;
74 static void _content_props_from(const HttpResponseType hrt
)
77 if (hrt
== HTTP_HEAD_RESPONSE
)
79 content_length
= _content_length_from_qmi();
80 content_type
= _content_type_from_qmi();
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
);
102 if (fo
.result
.file
!= NULL
)
104 fflush(fo
.result
.file
);
105 fclose(fo
.result
.file
);
106 fo
.result
.file
= NULL
;
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
);
134 return (EXIT_FAILURE
);
137 static gint
_chk_transfer_errors(CURL
*c
)
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
);
156 if (curl_code
!= CURLE_WRITE_ERROR
)
157 r
= _print_curl_errmsg(rc
, cc
);
158 else /* _write_cb returned error (0) */
161 if (fo
.result
.skip_retrieved_already
== FALSE
162 && force_skip_transfer
== FALSE
)
171 static gint
_set_io_errmsg()
173 io_errmsg
= lutil_strerror();
177 static gint
_build_fpath()
179 lutil_build_fpath_t b
;
182 if (g
->result
.fpath
!= NULL
) /* Skip re-building. */
183 return (EXIT_SUCCESS
);
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"),
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
229 if (g
->opts
.resume_from
>= 0)
230 fo
.overwrite_if_exists
= g
->opts
.overwrite_if_exists
;
232 fo
.overwrite_if_exists
= TRUE
;
234 if (content_length
>0)
235 fo
.content_bytes
= content_length
;
237 fo
.fpath
= g
->result
.fpath
;
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"));
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
)
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());
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
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()
371 c
= lutil_curl_handle_from(g
->q
);
374 g
->xperr(_("error: failed to retrieve the current curl "
375 "session handle from libquvi"));
376 return (EXIT_FAILURE
);
382 if (r
== EXIT_SUCCESS
)
384 curl_code
= curl_easy_perform(c
);
385 r
= _chk_transfer_errors(c
);
389 pbar
->flags
.failed
= (r
!= EXIT_SUCCESS
) ? TRUE
:FALSE
;
393 static gint
_exec_cmd()
395 struct lutil_exec_opts_s xopts
;
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
;
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
);
422 gint
lget_http_get(lget_t handle
)
426 memset(&fo
, 0, sizeof(struct lutil_file_open_s
));
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.
448 if (r
== EXIT_SUCCESS
|| fo
.result
.skip_retrieved_already
== TRUE
)
451 /* If --skip-transfer was defined, the */
452 if (transfer_skipped
== TRUE
)
455 return (_cleanup(r
));
458 /* vim: set ts=2 sw=2 tw=72 expandtab: */