Sunday, September 27, 2015

Uploading a file with the Google Drive API

This post describes how to upload a file in Google Drive using the REST API from C++, with cURL.
There are multiple ways to upload a file using the Google API, as per the documentation, however, the following strategy seems to be the most useful:
  1. Generate  a file id
  2. Initiate a resumable file upload with metadata (id, title, optional parent)
  3. Upload the actual file content
The prerequisite for this flow is to have an access token obtained via Google's authentication process using OAuth 2.0.

Let's go through each step and check the requests, responses and C++ code that makes it all work.
To generate an id we need to issue a GET call to this url  https://www.googleapis.com/drive/v2/files/generateIds?maxResults=1
The C++ function that does this action would look like this:
CURLcode cc;
curl_slist *slist = NULL;
slist = curl_slist_append(slist, "Accept: text/xml");
slist = curl_slist_append(slist, "Depth: infinity");
slist = curl_slist_append(slist, "Content-Type: text/xml");
slist = curl_slist_append(slist, "Connection: Close");
slist = curl_slist_append(slist, "Authorization: Bearer <your access token string>");

CURL *pcurl = curl_easy_init();
curl_easy_setopt(pcurl, CURLOPT_VERBOSE, 1);
curl_easy_setopt(pcurl, CURLOPT_URL, "https://www.googleapis.com/drive/v2/files/generateIds?maxResults=1");
curl_easy_setopt(pcurl, CURLOPT_HTTPHEADER, slist);
curl_easy_setopt(pcurl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_easy_setopt(pcurl, CURLOPT_WRITEFUNCTION, write_data);
cc = curl_easy_perform(pcurl);
if(CURLE_OK != cc){
    std::cout << "Error during curl_easy_perform: " << curl_easy_strerror(cc) << std::endl;
}

curl_slist_free_all(slist);
curl_easy_cleanup(pcurl);
A successful call result will look like this:
{
  "kind": "drive#generatedIds", 
  "ids": [
    "0B2MYkjLyxeqTQ0x4czZZaWZmU2s"
  ], 
  "space": "drive"
}
The second step is to initiate a resumable upload and specify some metadata for the file id that we just generated.
CURLcode cc;

std::string metadata = std::string(" { ") +
                           "\"title\": \"myfile\"," +
                           "\"id\": \""+ "0B2MYkjLyxeqTQ0x4czZZaWZmU2s" +"\"," +
                           "\"parents\": [ { \"id\": \""+ "0B0jHrhwKFmtTa2dPdmVnNG4zX0U" +"\" } ]" +
                           "} ";

std::string response;
std::string header;
curl_slist *slist = NULL;
slist = curl_slist_append(slist, "Depth: infinity");
slist = curl_slist_append(slist, "Content-Type: application/json; charset=UTF-8");
slist = curl_slist_append(slist, "Connection: Close");
slist = curl_slist_append(slist, "Authorization: Bearer <your access token string>");
slist = curl_slist_append(slist, "X-Upload-Content-Type: <mimeType of the file>");
slist = curl_slist_append(slist, "X-Upload-Content-Length: <content length in bytes>");

CURL *pcurl = curl_easy_init();
curl_easy_setopt(pcurl, CURLOPT_VERBOSE, 1);
curl_easy_setopt(pcurl, CURLOPT_URL, "https://www.googleapis.com/upload/drive/v2/files?uploadType=resumable");
curl_easy_setopt(pcurl, CURLOPT_HTTPHEADER, slist);
curl_easy_setopt(pcurl, CURLOPT_CUSTOMREQUEST, "POST");
curl_easy_setopt(pcurl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_easy_setopt(pcurl, CURLOPT_WRITEFUNCTION, write_data);
curl_easy_setopt(pcurl, CURLOPT_POSTFIELDS, metadata);
curl_easy_setopt(pcurl, CURLOPT_POSTFIELDSIZE, strlen(metadata));
curl_easy_setopt(pcurl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(pcurl, CURLOPT_HEADER, 1);
curl_easy_setopt(pcurl, CURLOPT_HEADERDATA, &header);

cc = curl_easy_perform(pcurl);
if(CURLE_OK != cc){
    std::cout << "Error during curl_easy_perform: " << curl_easy_strerror(cc) << std::endl;
}

curl_slist_free_all(slist);
curl_easy_cleanup(pcurl);
After this, in the response header we'll get a Location element with an url which we'll use for the actual upload.
To upload the actual file:
struct stat file_info;
/* get the file size of the local file */
stat(file, &file_info);

FILE* hd_src = fopen(file, "rb");
if(hd_src){
    CURLcode cc;
    curl_slist *slist = NULL;
    slist = curl_slist_append(slist, "Depth: infinity");
    slist = curl_slist_append(slist, "Connection: Close");

    CURL *pcurl = curl_easy_init();
    curl_easy_setopt(pcurl, CURLOPT_READFUNCTION, read_callback);
    curl_easy_setopt(pcurl, CURLOPT_UPLOAD, 1L);
    curl_easy_setopt(pcurl, CURLOPT_READDATA, hd_src);
    curl_easy_setopt(pcurl, CURLOPT_PUT, 1L);
    curl_easy_setopt(pcurl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_info.st_size);
    curl_easy_setopt(pcurl, CURLOPT_VERBOSE, 1);
    curl_easy_setopt(pcurl, CURLOPT_URL, "");
    curl_easy_setopt(pcurl, CURLOPT_HTTPHEADER, slist);
    curl_easy_setopt(pcurl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
    curl_easy_setopt(pcurl, CURLOPT_WRITEFUNCTION, write_data);
    cc =curl_easy_perform(pcurl);
    if(CURLE_OK != cc){
        std::cout << "Error during curl_easy_perform: " << curl_easy_strerror(cc) << std::endl;
    }

    curl_slist_free_all(slist);
    curl_easy_cleanup(pcurl);
    fclose(hd_src);
}
After this call executes successfully, in the response you will get JSON metadata about the uploaded file.
You can check the successful upload in the Google Drive web interface. You should have a file with the name of the "title" attribute in the folder you specified, if you used a parent id, otherwise, in the root folder.

No comments:

Post a Comment