Search code examples
node.jsgoogle-apigoogle-drive-apiservice-accountsgoogle-api-nodejs-client

Update file to change mime type results in "Invalid MIME type provided for the uploaded content"


The problem

I have a file up on Google drive, which was uploaded using Google drive api v3. The file in question was not converted to a Google sheet when it was uploaded. (Yes my code was messed up, trying to fix it.)

It was uploaded with the mime type of text/csv Image below shows its type as Comma separated values.

enter image description here

I have another file which was uploaded with the correct mime type application/vnd.google-apps.spreadsheet which caused the file to be converted to a Google sheet. Image below shows its type as Google sheet

enter image description here

Update file and fix it.

What I am trying to do is run a file.update on the file that was uploaded with the wrong mime type and change its mime type and upload the file again, causing it to be converted to a Google sheet.

Sending the following as the metadata for the file should cause it to be converted.

 'mimeType': 'application/vnd.google-apps.spreadsheet' 

Unfortunately it does not it results in the following error

errors: [ { domain: 'global', reason: 'invalidContentType', message: 'Invalid MIME type provided for the uploaded content.', locationType: 'other', location: 'media.mimeType' }

my code

const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');

// npm install googleapis
// https://developers.google.com/drive/api/v3/quickstart/nodejs

// Desktop app credentials from Google Cloud console.
const KEYFILEPATH = 'C:\\Youtube\\dev\\ServiceAccountCred.json';

const FILEIDTOUPDATE = '1YtJxL1WptkHUbDHNWpcbKwcRs7rPo_cY';

// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/drive'];

// Create a service account initialize with the service account key file and scope needed
const auth = new google.auth.GoogleAuth({
    keyFile: KEYFILEPATH,
    scopes: SCOPES
});

/**
 * Creates a new file on google drive and uploads it.
 * @param {google.auth.OAuth2} auth An authorized OAuth2 client.
 */
async function createAndUploadFile(auth) {

    const driveService = google.drive({version: 'v3', auth});
    let fileMetadata = {
        'name': 'sheet',
        'mimeType': 'application/vnd.google-apps.spreadsheet'   // will cause the file to be converted to the google drive type.
    };
    let media = {
        mimeType: 'text/csv',
        body: fs.createReadStream('sheet.csv')
    };
    await driveService.files.update({
        fileId : FILEIDTOUPDATE,
        resource: fileMetadata,
        media: media,
        fields: 'id'
    }, function (err, file) {
        if (err) {
            // Handle error
            console.error(err);
        } else {
            console.log('File Id: ', file.data.id);
        }
    });
}

createAndUploadFile(auth).catch(console.error);

what i have tried.

In order to test this that it wasn't actually an issue with my code i ran the code against the sheet that had been uploaded properly and it worked fine. According to the documentation MimeType should be writable

enter image description here

I am at a loss to explain this issue. I am wondering if the update method does not have the covert super power that the create method does.


Solution

  • About 'Invalid MIME type provided for the uploaded content.:

    • The method of "Files: update" of Drive API updates the file without changing the file ID.
    • The file ID depends on the mimeType.
      • For example, in your situation, the length of the file IDs of Google Docs files is different from the file ID of CSV file.

    When your situation is considered, it seems that you are trying to change the mimeType of CSV file to Google Spreadsheet using the method of "Files: update" of Drive API. If the mimeType can be changed with "Files: update" method, from above situation, the file ID is also required to be changed. I guessed that such situation might be the reason of the current issue.

    In order to check this hypothesis, I did the following experiments.

    Experiments:

    • When the PNG file is updated by the CSV file with changing the mimeType of text/csv, the PNG file is converted to the CSV file without changing the file ID.
    • The CSV file can be converted to PNG file using the PNG data without changing the file ID.
    • The mimeType of CSV file cannot be converted to image/png without changing the file content.
      • In order to change the mimeType, it seems that the data might be required to be used.
    • When the mimeType is not changed and the Spreadsheet is updated by the CSV file, this can be achieved.
    • When the request body includes to change the mimeType from Spreadsheet to CSV and the Spreadsheet is updated by the CSV file, the Spreadsheet is updated by the CSV data without the changing the mimeType.
    • When the request body includes to change the mimeType from CSV to Spreadsheet and the CSV file is updated by the CSV file, the error in your question occurs.
    • The CSV file is updated by the Google Spreadsheet, this cannot be achieved. Because the Google Docs files cannot be retrieved.

    From above results, it is considered the following situation.

    • It is considered that when the file is updated with "Files: update", when the file ID is NOT changed by the change of the mimeType, this can be achieved.
      • For example, CSV to PNG, PNG to CSV.
    • It is considered that when the file is updated with "Files: update", when the file ID is changed by the change of the mimeType, this cannot be achieved.
      • For example, CSV to Google Spreadsheet, Google Spreadsheet to CSV.
    • In order to change the mimeType, it seems that the data might be required to be used.
      • In my experiment, at least, media of 0 byte was required to be used for changing the mimeType.

    Also, above results might be able to answer for "writable" of mimeType for "Files: update".

    Modified script:

    About your script, for example, when the mimeType of CSV file is converted to Google Spreadsheet, the method of "Files: copy" of Drive API can be used. In this case, the file ID of the converted file (Spreadsheet) is changed from the CSV file. So, I thought that your script might be able to be modified as follows.

    const driveService = google.drive({ version: 'v3', auth });
    let fileMetadata = { name: 'sheet' };
    let media = {
      mimeType: 'text/csv',
      body: fs.createReadStream('sheet.csv'),
    };
    driveService.files.update(
      {
        fileId: FILEIDTOUPDATE,
        resource: fileMetadata,
        media: media,
        fields: 'id',
      },
      function (err, file) {
        if (err) {
          // Handle error
          console.error(err);
        } else {
          console.log('File Id: ', file.data.id);
          driveService.files.copy(
            {
              fileId: FILEIDTOUPDATE,
              resource: { mimeType: "application/vnd.google-apps.spreadsheet" },
            },
            function (err, { data }) {
              if (err) {
                console.error(err);
                return;
              }
              console.log(data);
            }
          );
        }
      }
    );
    
    • At above modified script, the CSV file of FILEIDTOUPDATE is updated by the file of sheet.csv using the method of "Files: update". And, the CSV file is converted to Google Spreadsheet using the method of "Files: copy".
    • If you are not required to leave the CSV file, you can delete the CSV file, after the method of "Files: copy" was done.

    References: