Masoud Rahimi
Masoud Rahimi

Reputation: 6051

unfinished download with curl in C

I'm using Curl library to create a simple C code with MSVC to download a file from a URL. The problem is if the connection breaks in the middle of download my code will freeze and the unfinished file hasn't removed from the directory. What I want is if the download failed the program must retry the connection or remove the unfinished file and then try again. I prefer to use C libraries rather than C++ libs. Here is the code I am using:

//lib for curl
#include <curl/curl.h>
#define CURL_STATICLIB

bool downloader3(string url, string file_path) {
    CURL *curl;
    FILE *fp;
    CURLcode res;
    curl = curl_easy_init();
    if (curl) {
        fp = fopen(file_path.c_str(), "wb");
        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
        res = curl_easy_perform(curl);
        //always cleanup
        curl_easy_cleanup(curl);
        fclose(fp);
        double val;
        res = curl_easy_getinfo(curl, CURLINFO_SPEED_DOWNLOAD, &val);
        if ((CURLE_OK == res) && (val>0))
            printf("Average download speed: %0.3f kbyte/sec.\n", val / 1024);
        if ((res == CURLE_OK)) {
            printf("Download Successful!\r\n");
            return true;
        }
        else {
            printf("Downlaod Failed!\r\n");
            remove(file_path.c_str());  //remove the temp file
            return false;
        }
    }
}

EDIT--- Thanks to Ring Ø answer. I modifed the code but I am looking for a resume capability that can resume the download of incomplete file.

bool downloader3(string url, string file_path) {
    CURL *curl;
    FILE *fp = NULL;
    CURLcode res;

    int status;
    int maxtries = 3;
    do {
        printf("Doing try # %d\r\n", maxtries);
        curl = curl_easy_init();
        if (curl) {
            fp = fopen(file_path.c_str(), "wb");
            curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
            curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
            curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
            curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L); // 30 seconds 
            res = curl_easy_perform(curl);
            //always cleanup
            curl_easy_cleanup(curl);
            fclose(fp);
            if ((res == CURLE_OK)) {
                printf("Download Successful!\r\n");
                break;
                //return true;
                }
            }
    } while (--maxtries);
    if (maxtries) { // was OK
        //curl_easy_cleanup(curl);  // clean curl / delete file?
        //fclose(fp);
        return true;
    }
    else {
        printf("Download Failed!\r\n");
        printf("file path is: %s", file_path.c_str());
        Sleep(5000);
        status = remove(file_path.c_str());  //remove the unfinished file
        if (status == 0)
            printf("%s file deleted successfully.\n", file_path);
        else
        {
            printf("Unable to delete the file\n");
        }
        return false;
    }
}

Upvotes: 1

Views: 1754

Answers (2)

aerth
aerth

Reputation: 557

What you are looking for is the RESUME_FROM feature. To use this you must know which byte you want to start the download from. In this example it is an upload but should be same setopt technique. Here is example usage from curl website:

CURL *curl = curl_easy_init();
if(curl) {
  curl_easy_setopt(curl, CURLOPT_URL, "ftp://example.com");

  /* resume upload at byte index 200 */
  curl_easy_setopt(curl, CURLOPT_RESUME_FROM, 200L);

  /* ask for upload */
  curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);

  /* set total data amount to expect */
  curl_easy_setopt(curl, CURLOPT_INFILESIZE, size_of_file);

  /* Perform the request */
  curl_easy_perform(curl);
}

source: https://curl.haxx.se/libcurl/c/CURLOPT_RESUME_FROM.html

Upvotes: 0

D&#233;j&#224; vu
D&#233;j&#224; vu

Reputation: 28850

You could set a timeout option

curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); // 30 seconds 

if the operation is not done within 30 seconds, the timeout is triggered. Then check the result value, in a while loop for instance

res = curl_easy_perform( ... );
if (res == CURLE_OK) {
    break;
}
// delete file
// keep retrying (add a counter if necessary)

See also the curl page.

Loop example

  int maxtries = 5;
  do {
     curl = curl_easy_init();
     if (curl) {
        ...
        res = curl_easy_perform( ... );
        if (res == CURLE_OK) {
           break;
        }

        // delete file, curl cleanup...
      }
  } while ( --maxtries );

  if (maxtries) { // was OK
     // clean curl / delete file?
  }

This is not the ideal solution, as you said, the download may take more or less time. This (should) prevent a never ending program, provided the timeout is big enough.

Curl library was known to have some problems in case of erratic connection - there could be something better nowadays, please try the latest stable build.

If you don't get a better answer within a few days, try to add a "Bounty" of 50 rep to attract more attention.

Upvotes: 2

Related Questions