18

Previously, I have a set of Google Drive API code, which works fine in the following scenarios

  1. Save new file to appdata
  2. Update previous file in appdata
  3. Save new file to non-appdata
  4. Update previous file in non-appdata

Few days ago, I encounter scenario 2 no longer work (Update previous file in appdata), whereas other scenarios still work without problem. I will be getting the following exception.

com.google.api.client.googleapis.json.GoogleJsonResponseException: 500 Internal Server Error
{
  "code": 500,
  "errors": [
    {
      "domain": "global",
      "message": "Internal Error",
      "reason": "internalError"
    }
  ],
  "message": "Internal Error"
}
    at com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:145)
    at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:113)
    at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:40)
    at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:423)
    at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:343)
    at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.execute(AbstractGoogleClientRequest.java:460)
    at org.yccheok.jstock.gui.Utils.updateFile(Utils.java:1414)

I'm using both DRIVE and DRIVE_APPDATA scope - authorizeDrive()

The code is as follow

Exception is being thrown at Line 1414, which is

com.google.api.services.drive.model.File updatedFile = service.files().update(fileId, file, mediaContent).setNewRevision(false).execute();

Searching previous file in appdata using query title contains 'jstock-fe78440e-e0fe-4efb' and trashed = false and 'appdata' in parents is completely fine. We're able to retrieve the previous file id without problem.

However, 500 Internal Server Error is being thrown, when we perform file updating using retrieved file id.

Some users encountered problem during searching in appdata (which is not my case). Search folder inside 'appdata' folder The suggested workaround is to add drive.readonly.metadata. I had tried that once, but it makes no difference.


Update

An excellent workaround proposed by Jon Skeet

I've managed to reproduce the issue. Without setNewRevision(false) it works - I realize that may not be feasible in all cases, but is it a reasonable workaround for you for the moment?

However, I will on hold on such workaround at this moment. We prefer to have setNewRevision(false), to prevent from causing increased use of the user's data storage quota - http://developers.google.com/drive/v2/reference/files/update


Short but complete source code to demonstrate the problem

  1. Create a client id & secret key. Update source code.
  2. Create a document.txt
  3. Run the source code first time, to upload document.txt to appdata folder. It should success. Check your uploaded file through online Google Drive. (Please refer to attachment)
  4. Run the source code for second time, to perform update on previous document.txt in appdata folder. The 500 Internal Server Error exception should be thrown.

enter image description here

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package insert;

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.FileContent;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.DriveScopes;
import com.google.api.services.drive.model.FileList;
import com.google.api.services.drive.model.ParentReference;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


public class Insert {

    private static com.google.api.services.drive.model.File searchFromGoogleDrive(Drive drive, String qString) {
        try {
            Drive.Files.List request = drive.files().list().setQ(qString);

            do {                
                FileList fileList = request.execute();

                com.google.api.services.drive.model.File file = null;

                for (com.google.api.services.drive.model.File f : fileList.getItems()) {

                    final String title = f.getTitle();

                    if (title == null || f.getDownloadUrl() == null || f.getDownloadUrl().length() <= 0) {
                        continue;
                    }

                    file = f;

                    break;
                }

                if (file != null) {
                    return file;
                }

                request.setPageToken(fileList.getNextPageToken());
            } while (request.getPageToken() != null && request.getPageToken().length() > 0);
        } catch (IOException ex) {
            log.error(null, ex);
            return null;
        }
        return null;
    }

    public static boolean saveToGoogleDrive(Credential credential, java.io.File file) {
        final String titleName = "document.txt";
        final String qString = "title contains '" + titleName + "' and trashed = false and 'appdata' in parents";        
        return _saveToGoogleDrive(credential, file, qString, "appdata");
    }

    public static Drive getDrive(Credential credential) {
        Drive service = new Drive.Builder(httpTransport, JSON_FACTORY, credential).setApplicationName("JStock").build();
        return service;
    }

    private static boolean _saveToGoogleDrive(Credential credential, java.io.File file, String qString, String folder) {
        Drive drive = getDrive(credential);

        // Should we new or replace?

        com.google.api.services.drive.model.File googleCloudFile = searchFromGoogleDrive(drive, qString);

        final String title = "document.txt";

        if (googleCloudFile == null) {
            String id = null;
            if (folder != null) {
                com.google.api.services.drive.model.File appData;
                try {
                    appData = drive.files().get(folder).execute();
                    id = appData.getId();
                } catch (IOException ex) {
                    log.error(null, ex);
                    return false;
                }
            }
            return null != insertFile(drive, title, id, file);
        } else {
            final com.google.api.services.drive.model.File oldFile = googleCloudFile;
            return null != updateFile(drive, oldFile.getId(), title, file);
        }
    }

    /**
     * Insert new file.
     *
     * @param service Drive API service instance.
     * @param title Title of the file to insert, including the extension.
     * @param parentId Optional parent folder's ID.
     * @param mimeType MIME type of the file to insert.
     * @param filename Filename of the file to insert.
     * @return Inserted file metadata if successful, {@code null} otherwise.
     */
    private static com.google.api.services.drive.model.File insertFile(Drive service, String title, String parentId, java.io.File fileContent) {
        // File's metadata.
        com.google.api.services.drive.model.File body = new com.google.api.services.drive.model.File();
        body.setTitle(title);

        // Set the parent folder.
        if (parentId != null && parentId.length() > 0) {
            body.setParents(
                Arrays.asList(new ParentReference().setId(parentId)));
        }

        // File's content.
        FileContent mediaContent = new FileContent("", fileContent);
        try {
            com.google.api.services.drive.model.File file = service.files().insert(body, mediaContent).execute();
            return file;
        } catch (IOException e) {
            log.error(null, e);
            return null;
        }
    }

    /**
     * Update an existing file's metadata and content.
     *
     * @param service Drive API service instance.
     * @param fileId ID of the file to update.
     * @param newTitle New title for the file.
     * @param newFilename Filename of the new content to upload.
     * @return Updated file metadata if successful, {@code null} otherwise.
     */
    private static com.google.api.services.drive.model.File updateFile(Drive service, String fileId, String newTitle, java.io.File fileContent) {
        try {
            // First retrieve the file from the API.
            com.google.api.services.drive.model.File file = service.files().get(fileId).execute();

            // File's new metadata.
            file.setTitle(newTitle);

            FileContent mediaContent = new FileContent("", fileContent);

            // Send the request to the API.
            com.google.api.services.drive.model.File updatedFile = service.files().update(fileId, file, mediaContent).setNewRevision(false).execute();

            return updatedFile;
        } catch (IOException e) {
            log.error(null, e);
            return null;
        }
    }

  private static String CLIENT_ID = "CLIENT_ID";
  private static String CLIENT_SECRET = "CLIENT_SECRET";

  private static String REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob";

  public static void main(String[] args) throws IOException {   
    GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
        httpTransport, JSON_FACTORY, CLIENT_ID, CLIENT_SECRET, Arrays.asList(DriveScopes.DRIVE_APPDATA, DriveScopes.DRIVE))
        .setAccessType("online")
        .setApprovalPrompt("auto").build();

    String url = flow.newAuthorizationUrl().setRedirectUri(REDIRECT_URI).build();
    System.out.println("Please open the following URL in your browser then type the authorization code:");
    System.out.println("  " + url);
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    String code = br.readLine();

    GoogleTokenResponse response = flow.newTokenRequest(code).setRedirectUri(REDIRECT_URI).execute();
    GoogleCredential credential = new GoogleCredential().setFromTokenResponse(response);

    java.io.File fileContent = new java.io.File("document.txt");
    saveToGoogleDrive(credential, fileContent);
  }

    private static final GsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();

    /** Global instance of the HTTP transport. */
    private static HttpTransport httpTransport;


    private static final Log log = LogFactory.getLog(Insert.class);

    static {
        try {
            // initialize the transport
            httpTransport = GoogleNetHttpTransport.newTrustedTransport();

        } catch (IOException ex) {
            log.error(null, ex);
        } catch (GeneralSecurityException ex) {
            log.error(null, ex);
        }
    }

}

Update on 1 January 2016

This problem seems gone. I guess Google Drive team had fixed it.

Community
  • 1
  • 1
Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875
  • Can you post a short but complete program which demonstrates the problem? (Without API keys etc, of course - but an obvious place to put them.) – Jon Skeet May 19 '14 at 06:08
  • I have some similar issue, but with different client. [See here](http://stackoverflow.com/questions/23734503/drive-file-update-error-500) – Raviprakash May 19 '14 at 12:23
  • @JonSkeet I had updated the question with a minimal workable. Please let me know, if you face any problem in re-producing the problem using the same source code. Thank you. – Cheok Yan Cheng May 19 '14 at 13:33
  • @CheokYanCheng: Thanks, will give it a go. (Can't you make it smaller my removing the `searchFromGoogleDrive` bit and just asking the user which mode to run in though?) – Jon Skeet May 19 '14 at 13:36
  • @JonSkeet Thank you. I feel extremely honor to have you looking at my question :) – Cheok Yan Cheng May 19 '14 at 13:40
  • @JonSkeet Hem... I'm not sure how I can remove `searchFromGoogleDrive `. Without perform search functionality, I cannot retrieve the previous uploaded file id, and can't perform "update on previous file" operation. – Cheok Yan Cheng May 19 '14 at 13:48
  • @CheokYanCheng: Ah, okay - that's fair enough then. – Jon Skeet May 19 '14 at 13:49
  • @JonSkeet In short, the logic of the code is, if this is the first time, it will perform new file `insert`. If the file is already in `appdata` folder, it will perform content `update`. – Cheok Yan Cheng May 19 '14 at 13:52
  • I've managed to reproduce the issue. Without `setNewRevision(false)` it works - I realize that may not be feasible in all cases, but is it a reasonable workaround for you for the moment? – Jon Skeet May 19 '14 at 14:42
  • Thank you! However, we prefer have `setNewRevision(false)`, to prevent from causing increased use of the user's data storage quota - https://developers.google.com/drive/v2/reference/files/update But, if that's the only workaround, we will need to adapt such. – Cheok Yan Cheng May 19 '14 at 15:47
  • @JonSkeet Do you want to post your proposal as answer? I will wait for few more days, to see is there a better workaround, without compromising user's data storage quota. – Cheok Yan Cheng May 19 '14 at 15:55

1 Answers1

18

Note: please do not treat this as an "official answer from Google". Although I work at Google, I don't work on the Drive API.

I've reproduced the problem, and reported it to the Drive API team, who may be able to provide more details. In the meantime, one workaround I've found is to remove the

setNewRevision(false)

part of your update call on line 1414. That's not an ideal workaround as it means you'll get a new revision for each update, which will use up storage quota. However, it does seem to avoid the problem you've seen.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194