Insert operation with convert flag TRUE fails - google-drive-api

I am generating reports resulting in WORD files using xdocreport.
From the generated report, I create a InputStreamContent with MIME-TYPE "application/vnd.openxmlformats-officedocument.wordprocessingml.document" (MS WORD - DOCX) to write to Google Drive :
// create Word file stream using xdocreport
OutputStream2InputStream outputStream = new OutputStream2InputStream(); // buffer
report.process(context, outputStream);
// create inputstream for Google Drive
InputStreamContent inputStream = new InputStreamContent("application/vnd.openxmlformats-officedocument.wordprocessingml.document",
outputStream.getInputStream());
inputStream.setLength(outputStream.size());
WRITING MSWORD DOCUMENT WORKS FINE (CONVERT= FALSE) :
File file = new File();
Insert insertOperation = service.files().insert(file, inputStream).setConvert(false);
file.setTitle("test.docx");
file.setMimeType(inputstream.getType());
File result = insertOperation.execute();
Resulting in a WORD DOCX file created on my Google Drive.
WRITING SAME INPUTSTREAM WITH CONVERT=TRUE FAILS
File file = new File();
Insert insertOperation = service.files().insert(file, inputStream).setConvert(true);
file.setTitle("test");
//file.setMimeType(inputstream.getType()); // what here ?
File result = insertOperation.execute();
RESULT
1. When NOT setting the mime type : newly created File result has 0 bytes and MIME-type: application/vnd.google-apps.kix
2. When setting the mime type : MIME-TYPE set to "application/vnd.google-apps.document" and convert = true, results in 400: BAD REQUEST.
What am I doing wrong ?

This is a common problem. Don't set a MIME type in the request metadata. Google Drive will decide the MIME type to convert to.
Your line marked // what here ? should be left out.

All what you need to do is to update your getType() with the correct .docx MIME-type.
docx=> application/vnd.openxmlformats-officedocument.wordprocessingml.document
I had the same issue and this piece of code fixed it!

Related

Retrieving binary file from post request

Sending a POST request (Apache httpclient, here Kotlin source code):
val httpPost = HttpPost("http://localhost:8000")
val builder = MultipartEntityBuilder.create()
builder.addBinaryBody("file", File("testFile.zip"),
ContentType.APPLICATION_OCTET_STREAM, "file.ext")
val multipart = builder.build()
httpPost.entity = multipart
val r = httpClient.execute(httpPost)
r.close()
I receive the request in my post handler as a via spark-java Request-object. How do I retrieve the original file (plus the file name as a bonus) from the post request? The request.bodyAsBytes() method seems to add some bytes because the body is larger than the original file.
Thanks, Jörg
Near the bottom of Spark's Documentation page there is a section "Examples and FAQ". The first example is "How do I upload something?".
From there, it links further to an example on GitHub.
In short:
post("/yourUploadPath", (request, response) -> {
request.attribute("org.eclipse.jetty.multipartConfig", new MultipartConfigElement("/temp"));
try (InputStream is = request.raw().getPart("file").getInputStream()) {
// Use the input stream to create a file
}
return "File uploaded";
});
To access the original file name:
request.raw().getPart("file").getSubmittedFileName()
To handle multiple files or parts, I usually have code similar to the following (assuming only files are included in the multi-part encoded upload):
for (Part part : req.raw().getParts()) {
try (InputStream stream = part.getInputStream()) {
String filename = part.getSubmittedFileName();
// save the input stream to the filesystem, and the filename to a database
}
}

Unable to update Google Drive files using Drive API v3 -- The resource body includes fields which are not directly writable

I am trying to use Google Drive API (v3) to make updates to documents
in Google Drive.
I have read this migration guide:
Google Drive API v3 Migration
And coded it to make a new empty File() with the details I want to update
and then calling execute() with that and the file ID.
But i am still getting an error. Can anyone point out where I am doing wrong?
thanks alot!!
Error:
{
"code" : 403,
"errors" : [{
"domain" : "global",
"message" : "The resource body includes fields which are not directly writable.",
"reason" : "fieldNotWritable"
}],
"message" : "The resource body includes fields which are not directly writable."
}
Code snippet below:
File newFileDetails = new File();
FileList result = service2.files().list()
.setPageSize(10)
.setFields("nextPageToken, files(id, name)")
.execute();
List<File> files = result.getFiles();
if (files == null || files.size() == 0) {
System.out.println("No files found.");
} else {
System.out.println("Files:");
for (File file : files) {
if (file.getName().equals("first_sheet")) {
System.out.printf("%s (%s)\n", file.getName(), file.getId());
newFileDetails.setShared(true);
service2.files().update(file.getId(), newFileDetails).execute();
}
}
}
I had the same issue and found a solution. The key point is: you must create a new File object without Id and use it in update() method. Here is a piece of my code:
val oldMetadata = service!!.files().get(fileId.id).execute()
val newMetadata = File()
newMetadata.name = oldMetadata.name
newMetadata.parents = oldMetadata.parents
newMetadata.description = idHashPair.toDriveString()
val content = ByteArrayContent("application/octet-stream", fileContent)
val result = service!!.files().update(fileId.id, newMetadata, content).execute()
It works. I hope it'll help you.
Referring to https://developers.google.com/drive/v3/reference/files#resource-representations, you can see that shared isn't a writable field. If you think about it, this makes perfect sense. You can share a file by adding a new permission, and you can check if a file has been shared by reading the shared property. But saying a file is shared, other than by actually sharing it, makes no sense.
in the code it looks like this
Drive service... // your own declared implementation of service
File file = new File(); //using the com.google.api.services.drive.model package
// part where you set your data to file like:
file.setName("new name for file");
String fileID = "id of file, which you want to change";
service.files().update(fileID,file).execute();
trying to change the fields from remote files, and rewriting to this file can throw the security exception like exception below.
but it is not a solution for your question.
If you want to share file to another google account by email, you can do it with reimplementing authorization to authorization with using service account of your app, and the add the needed email, as owner of the file.
I was doing the same thing. My goal was to share my file programmatically with my Python code.
And yes, I was getting the same error:
"The resource body includes fields which are not directly writable"
I solved this problem by adding the service's email address of my Virtual Machine (I created it on my Compute Engine dashboard) to Editors of the file.
Then I ran this Python code in my VM:
from googleapiclient.discovery import build
from oauth2client.service_account import ServiceAccountCredentials
# Took the json file from my Google Cloud Platform (GCP) → IAM & Admin → Service Accounts:
service_key_file = 'service_key.json'
scope = 'https://www.googleapis.com/auth/drive'
credentials = ServiceAccountCredentials.from_json_keyfile_name(service_key_file, scopes=scope)
driveV3 = build('drive', 'v3', credentials=credentials)
fileId = '1ZP1xZ0WaH8w2yaQTSx99gafNZWawQabcdVW5DSngavQ' # A spreadsheet file on my GDrive.
newGmailUser = 'testtest#gmail.com'
permNewBody = {
'role': 'reader',
'type': 'user',
'emailAddress': newGmailUser,
}
driveV3.permissions().create(fileId=fileId, body=permNewBody).execute()
print(f"""The file is now shared with this user:
{newGmailUser}\n
See the file here:
https://docs.google.com/spreadsheets/d/1ZP1xZ0WaH8w2yaQTSx99gafNZWawQabcdVW5DSngavQ""")

Import Google app script project from JSON file

In Google Drive, it's possible to download an app script project as a .json file.
When such file is imported back to a Google Drive it's not properly associated with Google Script editor app.
Is there any way to do it properly?
Importing and exporting of Apps Script files requires the use of the import/export API.
To modify an existing script you will need to have a Oauth2 token with the scope of: https://www.googleapis.com/auth/drive.scripts
For updating a file you will "PUT" the updated JSON to:
https://www.googleapis.com/upload/drive/v2/files/{FileId}
The Apps Script file looks like
{
files:
[
{
name:{fileName},
type:{/* server_js or html */},
source:{/* source code for this file */},
id:{ /* Autogenerated. Omit this key for a new file, or leave value unmodified for an updated file */},
},
{...}
]
}
To add a file:
Add an object to the files array with the keys name, type, source
To modify a file:
Modify the values of name, type, or source of the file object but do not modify the id.
When you PUT the file back make sure you put the entire files array with your modifications, not just the new file object.
To make the modification in GAS itself would look like:
var scriptFiles = JSON.parse(downloadedJSONFile);
scriptFiles.files.push({"name":fileName,"type":fileType,"source":source});
var url = "https://www.googleapis.com/upload/drive/v2/files/"+scriptId;
var parameters = { method : 'PUT',
headers : {'Authorization': 'Bearer '+ tokenWithProperScope,
payload : JSON.stringify(scriptFiles),
contentType:'application/vnd.google-apps.script+json',
muteHttpExceptions:true};
var response = UrlFetchApp.fetch(url,parameters);
You will get a response code of 200 for a successful change. The response text will include the entire new JSON files with the assigned id to the file you added.
Fine more at:
https://developers.google.com/apps-script/import-export
Set the mimetype as application/vnd.google-apps.script

Upload CSV to Google Drive Spreadsheet using Drive v2 API

How can I upload a local CSV file to Google Drive using the Drive API v2 so that the uploaded file is in the native Google Spreadsheet format. Preferably in Python, but a raw HTTP request will suffice.
What I tried:
request body content-type: 'application/vnd.google-apps.spreadsheet', media_body content-type: 'text/csv'. --> 401 Bad Request
request body content-type: 'application/vnd.google-apps.spreadsheet', media_body content-type: 'application/vnd.google-apps.spreadsheet'. --> 400 Bad Request
... (a couple of others such as leaving a property out and similar, usually got 400 or Drive didn't recognise it as a native spreadsheet)
Your insert request should specify text/csv as the content-type.
The trick to get the file converted is to add the ?convert=true query parameter to the request url:
https://developers.google.com/drive/v2/reference/files/insert
(Mar 2017) Note, while the question specifically asks about Drive API v2, developers should know that the Google Drive API team released v3 at the end of 2015, and in that release, insert() changed names to create() so as to better reflect the file operation. There's also no more convert flag -- you just specify MIMEtypes... imagine that!
The documentation has also been improved: there's now a special guide devoted to uploads (simple, multipart, and resumable) that comes with sample code in Java, Python, PHP, C#/.NET, Ruby, JavaScript/Node.js, and iOS/Obj-C to upload a file and another that imports a CSV file as a Google Sheet.
Just to show how straightforward it is, below is one alternate Python solution (to the sample in the docs) for short files ("simple upload") where you don't need the apiclient.http.MediaFileUpload class. This snippet assumes your auth code works where your service endpoint is DRIVE with a minimum auth scope of https://www.googleapis.com/auth/drive.file.
# filenames & MIMEtypes
DST_FILENAME = 'inventory'
SRC_FILENAME = DST_FILENAME + '.csv'
SHT_MIMETYPE = 'application/vnd.google-apps.spreadsheet'
CSV_MIMETYPE = 'text/csv'
# Import CSV file to Google Drive as a Google Sheets file
METADATA = {'name': DST_FILENAME, 'mimeType': SHT_MIMETYPE}
rsp = DRIVE.files().create(body=METADATA, media_body=SRC_FILENAME).execute()
if rsp:
print('Imported %r to %r (as %s)' % (SRC_FILENAME, DST_FILENAME, rsp['mimeType']))
Claudio Cherubino's answer is correct -- you have to add the parameter manually. Since you asked in Python though, here's a concrete example:
body = {
'mimeType':'text/csv',
'title': 'title'
}
# service: your authenticated service
# media: your apiclient.http.MediaFileUpload object, with 'text/csv' mimeType
req = service.files().insert(media_body=media, body=body)
# patch the uri to ensure conversion, as the documented kwarg seems to be borked.
# you may need to use '?convert=true' depending on the uri, not taking that into
# account here for sake of simplicity.
req.uri = req.uri + '&convert=true'
# now we can execute the response.
resp = req.execute()
# should be OK
assert resp['mimeType'] == u'application/vnd.google-apps.spreadsheet'
Java :
//Insert a file
File body = new File();
body.setTitle("CSV");
body.setDescription("A test document");
body.setMimeType("text/csv");
java.io.File fileContent = new java.io.File("document.csv");
FileContent mediaContent = new FileContent("text/csv", fileContent);
Insert insert = service.files().insert(body, mediaContent);
insert.setConvert(true);
File file = insert.execute();
System.out.println("File ID: " + file.getId());
The best way to get started is using the web form at
https://developers.google.com/drive/v2/reference/files/insert#try-it

Programmatically convert OneNote sections to XPS

I need a way to programmatically convert OneNote sections files (.one) to XPS format. I know how it works for Excel and Word, but Interop.OneNote is different.
You would use the Publish function (http://msdn.microsoft.com/en-us/library/gg649853.aspx), e.g.:
using Microsoft.Office.Interop.Onenote;
...
Application onApp = new Application();
string sectionid = "your section id here..."; //could also be a page or notebook id
string path = "your filepath here..."; //e.g. C:\Users\MyUsername\Documents\hi.xps
onApp.Publish(sectionid, path, PublishFormat.pfXPS);