cURL / Mailing Lists / curl-users / Single Mail

curl-users

Re: How to resume file download through FTP ?

From: Dan Fandrich <dan_at_coneharvesters.com>
Date: Thu, 23 Feb 2012 14:41:15 +0100

On Thu, Feb 23, 2012 at 03:12:40PM +0530, Yuvi yuvi wrote:
> I have searched a lot but don't find any sample code for resume download
> through File Transfer Code. I know that curl support this feature and I can
> able to do this using command prompt. But how to do it programmatically. Even I
> tired to write one but no luck, this code working when connection is closed at
> my lapi but if connection is closed by remote site it is not working :

You can use the --libcurl option in the command-line tool to see what
options it sets. Resuming also requires seek logic in the client,
though, which --libcurl won't give you.

> #include <stdlib.h>
> #include <stdio.h>
>
> #include <curl/curl.h>
>
>
> struct FtpFile {
>
> � const char *filename;
>
> � FILE *stream;
> };
>
> static size_t my_fwrite(void *buffer, size_t size, size_t nmemb, void *stream)
>
> {
> � struct FtpFile *out=(struct FtpFile *)stream;
>
> � if(out && !out->stream) {
>
> � � /* open file for writing */
> � � out->stream=fopen(out->filename, "wb");
>
> � � if(!out->stream)
> � � � return -1; /* failure, can't open file to write */
>
> � }
> � return fwrite(buffer, size, nmemb, out->stream);
>
> }
>
>
>
> static size_t append_fwrite(void *buffer, size_t size, size_t nmemb, void *stream)
>
> {
> � struct FtpFile *out=(struct FtpFile *)stream;
>
> � if(out && !out->stream) {
>
> � �// �open file for writing
> � � out->stream=fopen(out->filename, "a+");

The other fopen uses the 'b' modifier, so this ought to as well.

> � � if(!out->stream)
> � � � return -1; �//failure, can't open file to write
>
> � }
> � return fwrite(buffer, size, nmemb, out->stream);
>
> }
>
>
>
> /* parse headers for Content-Length */
> size_t getcontentlengthfunc(void *ptr, size_t size, size_t nmemb, void *stream)
>
> {
> � int r;
> � long len = 0;
>
>
> � /* _snscanf() is Win32 specific */
> � r = sscanf(ptr, �"Content-Length: %ld\n", &len);

This will fail if the server sends this line with different case. I
believe recent versions of libcurl guarantee that curl_easy_getinfo with
CURLINFO_CONTENT_LENGTH_DOWNLOAD will return that value (when available
at all) by the time the first write callback is called. That would be a
more reliable way of determining the length.

>
> � if (r) /* Microsoft: we don't read the specs */
>
> � � *((long *) stream) = len;
>
> � return size * nmemb;
> }
>
>
> /* discard downloaded data */
> size_t discardfunc(void *ptr, size_t size, size_t nmemb, void *stream)

This isn't referenced anywhere. And throwing out all downloaded data is
usually pretty wasteful. Using NOBODY as you've done is much better.

> {
> � return size * nmemb;
>
> }
>
>
>
>
> int download(CURL *curlhandle, const char * remotepath,long timeout, long tries)
>
> {
>
> � long uploaded_len = 0;
>
> � CURLcode r = CURLE_GOT_NOTHING;
> � int c;
>
> � struct FtpFile ftpfile={
> � � "dev.zip", �//name to store the file as if succesful
>
> � � NULL
> � };
>
>
>
>
> � curl_easy_setopt(curlhandle, CURLOPT_URL, remotepath);
>
>
> � if (timeout)
> � � curl_easy_setopt(curlhandle, CURLOPT_FTP_RESPONSE_TIMEOUT, timeout);
>
>
> � curl_easy_setopt(curlhandle, CURLOPT_HEADERFUNCTION, getcontentlengthfunc);
>
> � curl_easy_setopt(curlhandle, CURLOPT_HEADERDATA, &uploaded_len);

I don't see where this value is used.

>
> � curl_easy_setopt(curlhandle, CURLOPT_WRITEFUNCTION, my_fwrite);
>
> � curl_easy_setopt(curlhandle, CURLOPT_WRITEDATA, &ftpfile);
>
> � curl_easy_setopt(curlhandle, CURLOPT_FTPPORT, "-"); /* disable passive mode */
>
> � curl_easy_setopt(curlhandle, CURLOPT_FTP_CREATE_MISSING_DIRS, 1L);
>
>
> � curl_easy_setopt(curlhandle, CURLOPT_VERBOSE, 1L);
>
>
> � for (c = 0; (r != CURLE_OK) && (c < tries); c++) {
>
> � � /* are we resuming? */
> � � if (c) { /* yes */

This logic is too simplistic. The first attempt may have failed well
before the connection to the server was even established.

> � � � /* determine the length of the file already written */
> � � � � fputs("retrying.............",stderr);
>
> � � � curl_easy_setopt(curlhandle, CURLOPT_NOBODY, 1L);
>
> � � � curl_easy_setopt(curlhandle, CURLOPT_HEADER, 1L);
>
>
> � � � r = curl_easy_perform(curlhandle);

Using curl_easy_getinfo eliminates the need for this separate transfer.

> � � � if (r != CURLE_OK)
>
> � � � � continue;
>
> � � � curl_easy_setopt(curlhandle, CURLOPT_NOBODY, 0L);
>
> � � � curl_easy_setopt(curlhandle, CURLOPT_HEADER, 0L);
>
>
>
>
> � � � curl_easy_setopt(curlhandle, CURLOPT_APPEND, append_fwrite);

CURLOPT_APPEND takes a long, not a function pointer. I think you want
CURLOPT_WRITEFUNCTION here.

What you're missing is an ftell or fstat call and the CURLOPT_RANGE or
CURLOPT_RESUME_FROM option to have the server start sending data from
the last location downloaded.

>
> � � � curl_easy_setopt(curlhandle, CURLOPT_VERBOSE, 1L);
>
> � � }
> � � else { /* no */
>
> � � � curl_easy_setopt(curlhandle, CURLOPT_APPEND, 0L);
>
> � � � curl_easy_setopt(curlhandle, CURLOPT_VERBOSE, 1L);
>
> � � }
>
> � � r = curl_easy_perform(curlhandle);
>
> � }
>
>
>
> � if (r == CURLE_OK)
>
> � � return 1;
> � else {
>
> � � fprintf(stderr, "%s\n", curl_easy_strerror(r));
>
> � � return 0;
> � }
> }
>
>
> int main(int c, char **argv)
>
> {
> � CURL *curlhandle = NULL;
>
>
> � curl_global_init(CURL_GLOBAL_ALL);
> � curlhandle = curl_easy_init();
>
>
> � download(curlhandle, "ftp://root:password@192.168.10.1/dev.zip", �0, 100);
>
>
> � curl_easy_cleanup(curlhandle);
> � curl_global_cleanup();
>
>
> � return 0;
> }

>>> Dan

-------------------------------------------------------------------
List admin: http://cool.haxx.se/list/listinfo/curl-users
FAQ: http://curl.haxx.se/docs/faq.html
Etiquette: http://curl.haxx.se/mail/etiquette.html
Received on 2012-02-23