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