Google Drive Api (V2) `corpora` choices - google-drive-api

What are the possible choices for corpora and what do they mean?
I want to get a list of all the files I have access too inside a directory, Using .files().list( ... )
https://developers.google.com/drive/v2/reference/files/list
I used to run the following code which worked:
SearchParameterString = "'" + FolderId + "' in parents"
#NOTE there is a return limit of 460 files (falsly documented as 1000)
DriveFileItems = []
PageToken = None
while True:
try:
DriveFilesObject = Service.files().list(
q = SearchParameterString,
#corpora = 'domain', #'default',#SearchOwners,
corpus = 'DOMAIN', #----> DEPRICATED!!!
maxResults = 200,
pageToken = PageToken,
).execute()
DriveFileItems.extend(DriveFilesObject['items'])
PageToken = DriveFilesObject.get('nextPageToken')
if not PageToken:
break
except errors.HttpError, error:
print 'An error occurred: %s' % error
break
And the above code broke for me on June 25. With the following error message:
https://www.googleapis.com/drive/v2/files?q=%27___myFolderIdHere___%27+in+parents&alt=json&corpus=DOMAIN&maxResults=200 returned "Invalid query">
And I figured out that it was because they deprecated parameter corpus in favor of corpora
What are the possible choices for google drive api corpora ?
And what do they mean?
corpora = 'domain', #DOES NOT WORK
How do I make sure I am getting the full list of files, instead of just the files I own? (previously I had to switch from DEFAULT to DOMAIN because I had all sorts of problems from not getting full file lists, and ended up uploading many many duplicates while trying to use drive api to sync directories across machines)
I found this:
https://github.com/google/google-api-go-client/issues/218
and this:
https://godoc.org/golang.org/x/oauth2/jwt#Config
but don't really know what it means to impersonate a user, nor do I really think I want to do so.
EDIT: I happen to be using python - but the question is language agnostic

In case you are using Google Drive Java API, then most portably you are missing one of following arguments:
drive_id
include_items_from_all_drives=true
supports_all_drives=true
corpora=drive
Specifying those arguments is especially important in case you want to find files only from one shared drive.
private void readFiles(Drive drive) {
FileList result;
try {
result = drive.files().list()
.setQ("'" + DOCUMENTS_FOLDER_ID + "' in parents")
.setDriveId(P10_DRIVE_ID)
.setIncludeItemsFromAllDrives(true)
.setSupportsAllDrives(true)
.setCorpora("drive")
.setPageSize(100)
.setFields("nextPageToken,files(id, name, webViewLink)")
.execute();
List<File> files = result.getFiles();
if (files == null || files.isEmpty()) {
System.out.println("No files found.");
} else {
System.out.println("Files:");
for (File file : files) {
System.out.printf("%s (%s) (%s)\n", file.getName(), file.getId(), file.getWebViewLink());
}
}
} catch (IOException e) {
e.printStackTrace();
}
}

I think you can see that in the Migrate from Drive v2, the only options are:
Method Parameter Alternative
files.list corpus=DEFAULT corpus=user
files.list corpus=DOMAIN corpus=domain

Using drive api v2:
corpora = 'domain' doesn't work at all...
corpus = 'DOMAIN' doesn't work at all...
corpora = 'default' seems to do the trick.
corpora = 'default' works now the same way corpus = 'DOMAIN' worked before.
Files created by other users are not omitted (as I desire). Google changed this functionality without announcing it, and it broke out of nowhere for me on June 25, 2017.

Related

Dart / flutter: download or read the contents of a Google Drive file

I have a public (anyone with the link can view) file on my Google Drive and I want to use the content of it in my Android app.
From what I could gather so far, I need the fileID, the OAuth token and the client ID - these I already got. But I can't figure out what is the exact methodology of authorising the app or fetching the file.
EDIT:
Simply reading it using file.readAsLines didn't work:
final file = new File(dogListTxt);
Future<List<String>> dogLinks = file.readAsLines();
return dogLinks;
The dogLinks variable isn't filled with any data, but I get no error messages.
The other method I tried was following this example but this is a web based application with explicit authorization request (and for some reason I was never able to import the dart:html library).
The best solution would be if it could be done seamlessly, as I would store the content in a List at the application launch, and re-read on manual refresh button press.
I found several old solutions here, but the methods described in those doesn't seem to work anymore (from 4-5 years ago).
Is there a good step-by-step tutorial about integrating the Drive API in a flutter application written in dart?
I had quite a bit of trouble with this, it seems much harder than it should be. Also this is for TXT files only. You need to use files.export() for other files.
First you need to get a list fo files.
ga.FileList textFileList = await drive.files.list(q: "'root' in parents");
Then you need to get those files based on ID (This is for TXT Files)
ga.Media response = await drive.files.get(filedId, downloadOptions: ga.DownloadOptions.FullMedia);
Next is the messy part, you need to convert your Media object stream into a File and then read the text from it. ( #Google, please make this easier.)
List<int> dataStore = [];
response.stream.listen((data) {
print("DataReceived: ${data.length}");
dataStore.insertAll(dataStore.length, data);
}, onDone: () async {
Directory tempDir = await getTemporaryDirectory(); //Get temp folder using Path Provider
String tempPath = tempDir.path; //Get path to that location
File file = File('$tempPath/test'); //Create a dummy file
await file.writeAsBytes(dataStore); //Write to that file from the datastore you created from the Media stream
String content = file.readAsStringSync(); // Read String from the file
print(content); //Finally you have your text
print("Task Done");
}, onError: (error) {
print("Some Error");
});
There currently is no good step-by-step tutorial, but using https://developers.google.com/drive/api/v3/manage-downloads as a reference guide for what methods to use in Dart/Flutter via https://pub.dev/packages/googleapis: to download or read the contents of a Google Drive file, you should be using googleapis/Drive v3, or specifically, the methods from the FilesResourceApi class.
drive.files.export(), if this is a Google document
/// Exports a Google Doc to the requested MIME type and returns the exported content. Please note that the exported content is limited to 10MB.
drive.files.get(), if this something else, a non-Gdoc file
/// Gets a file's metadata or content by ID.
Simplified example:
var drive = new DriveApi(http_client);
drive.files.get(fileId).then((file) {
// returns file
});
However, what I discovered was that this Dart-GoogleAPIs library seemed to be missing a method equivalent to executeMediaAndDownloadTo(outputStream). In the original Google Drive API v3, this method adds the alt=media URL parameter to the underlying HTTP request. Otherwise, you'll get the error, which is what I saw:
403, message: Export requires alt=media to download the exported
content.
And I wasn't able to find another way to insert that URL parameter into the current request (maybe someone else knows?). So as an alternative, you'll have to resort to implementing your own Dart API to do the same thing, as hinted by what this OP did over here https://github.com/dart-lang/googleapis/issues/78: CustomDriveApi
So you'll either:
do it through Dart with your own HttpClient implementation and try to closely follow the REST flow from Dart-GoogleAPIs, but remembering to include the alt=media
or implement and integrate your own native-Android/iOS code and use the original SDK's convenient executeMediaAndDownloadTo(outputStream)
(note, I didn't test googleapis/Drive v2, but a quick examination of the same methods looks like they are missing the same thing)
I wrote this function to get file content of a file using its file id. This is the simplest method I found to do it.
Future<String> _getFileContent(String fileId) async {
var response = await driveApi.files.get(fileId, downloadOptions: DownloadOptions.fullMedia);
if (response is! Media) throw Exception("invalid response");
return await utf8.decodeStream(response.stream);
}
Example usage:
// save file to app data folder with 150 "hello world"s
var content = utf8.encode("hello world" * 150);
driveApi.files
.create(File(name: fileName, parents: [appDataFolder]),
uploadMedia: Media(Stream.value(content), content.length))
.then((value) {
Log().i("finished uploading file ${value.id}");
var id = value.id;
if (id != null) {
// after successful upload, read the recently uploaded file content
_getFileContent(id).then((value) => Log().i("got content is $value"));
}
});

Deleted files status unreliably reported in the new Google Drive Android API (GDAA)

This issue has been bugging me since the inception of the new Google Drive Android Api (GDAA).
First discussed here, I hoped it would go away in later releases, but it is still there (as of 2014/03/19). The user-trashed (referring to the 'Remove' action in 'drive.google.com') files/folders keep appearing in both the
Drive.DriveApi.query(_gac, query), and
DriveFolder.queryChildren(_gac, query)
as well as
DriveFolder.listChildren(_gac)
methods, even if used with
Filters.eq(SearchableField.TRASHED, false)
query qualifier, or if I use a filtering construct on the results
for (Metadata md : result.getMetadataBuffer()) {
if ((md == null) || (!md.isDataValid()) || md.isTrashed()) continue;
dMDs.add(new DrvMD(md));
}
Using
Drive.DriveApi.requestSync(_gac);
has no impact. And the time elapsed since the removal varies wildly, my last case was over 12 HOURS. And it is completely random.
What's worse, I can't even rely on EMPTY TRASH in 'drive.google.com', it does not yield any predictable results. Sometime the file status changes to 'isTrashed()' sometimes it disappears from the result list.
As I kept fiddling with this issue, I ended up with the following superawfulhack:
find file with TRASH status equal FALSE
if (file found and is not trashed) {
try to write content
if ( write content fails)
create a new file
}
Not even this helps. The file shows up as healthy even if the file is in the trash (and it's status was double-filtered by query and by metadata test). It can even be happily written into and when inspected in the trash, it is modified.
The conclusion here is that a fix should get higher priority, since it renders multi-platform use of Drive unreliable. It will be discovered by developers right away in the development / debugging process, steering them away.
While waiting for any acknowledgement from the support team, I devised a HACK that allows a workaround for this problem. Using the same principle as in SO 22295903, the logic involves falling back to RESTful API. Basically, dropping the LIST / QUERY functionality of GDAA.
The high level logic is:
query the RESTful API to retrieve the ID/IDs of file(s) in question
use retrieved ID to get GDAA's DriveId via 'fetchDriveId()'
here are the code snippets to document the process:
1/ initialize both GDAA's 'GoogleApiClient' and RESTful's 'services.drive.Drive'
GoogleApiClient _gac;
com.google.api.services.drive.Drive _drvSvc;
void init(Context ctx, String email){
// build GDAA GoogleApiClient
_gac = new GoogleApiClient.Builder(ctx).addApi(com.google.android.gms.drive.Drive.API)
.addScope(com.google.android.gms.drive.Drive.SCOPE_FILE).setAccountName(email)
.addConnectionCallbacks(ctx).addOnConnectionFailedListener(ctx).build();
// build RESTFul (DriveSDKv2) service to fall back to
GoogleAccountCredential crd = GoogleAccountCredential
.usingOAuth2(ctx, Arrays.asList(com.google.api.services.drive.DriveScopes.DRIVE_FILE));
crd.setSelectedAccountName(email);
_drvSvc = new com.google.api.services.drive.Drive.Builder(
AndroidHttp.newCompatibleTransport(), new GsonFactory(), crd).build();
}
2/ method that queries the Drive RESTful API, returning GDAA's DriveId to be used by the app.
String qry = "title = 'MYFILE' and mimeType = 'text/plain' and trashed = false";
DriveId findObject(String qry) throws Exception {
DriveId dId = null;
try {
final FileList gLst = _drvSvc.files().list().setQ(query).setFields("items(id)").execute();
if (gLst.getItems().size() == 1) {
String sId = gLst.getItems().get(0).getId();
dId = Drive.DriveApi.fetchDriveId(_gac, sId).await().getDriveId();
} else if (gLst.getItems().size() > 1)
throw new Exception("more then one folder/file found");
} catch (Exception e) {}
return dId;
}
The findObject() method above (again I'm using the 'await()' flavor for simplicity) returns the the Drive objects correctly, reflecting the trashed status with no noticeable delay (implement in non-UI thread).
Again, I would strongly advice AGAINST leaving this in code longer than necassary since it is a HACK with unpredictable effect on the rest of the system.

Google Drive iOS SDK

I want to check if a folder exists AND only if it doesn't exist create a new one. However, that folder could be a subfolder of root or another folder. So how can I do something like mkdirs on Unix where you give it a path and it creates all directories in the path?
BTW - The SDK is a bit frustrating to use as it appears to not have a way to use filesystem paths. Instead, you have to lots of queries and callbacks which is quite messy. W
This is what I use to create a folder:
- (void)createFolder:(NSString *)folderName completion:(void (^)(GTLDriveFile * file, NSError *))handler {
NSLog(#"createFolder:%# completion:", folderName);
GTLDriveFile *folder = [GTLDriveFile object];
folder.title = folderName;
folder.mimeType = #"application/vnd.google-apps.folder";
GTLQueryDrive *query = [GTLQueryDrive queryForFilesInsertWithObject:folder uploadParameters:nil];
[self.driveService executeQuery:query completionHandler:^(GTLServiceTicket *ticket, GTLDriveFile *updatedFile, NSError *error) {
if (error == nil) {
NSLog(#"Created folder");
handler(updatedFile, nil);
} else {
NSLog(#"An error occurred: %#", error);
handler(updatedFile, error);
}
}];
}
There is no easy way.
The *nix filesystem is hierarchical, whereas the Google Drive filesystem is not. There are things which the UI and API refers to a "folders", but these are little more than tags/labels.
So if you want a folder hierarchy, you'll need to build it yourself (probably as you've figured out). There are no shortcuts.
You will probably find starting your app by doing a search for all folders
q = "mimeType = 'application/vnd.google-apps.folder'"
is the way to go

What's the right way to find files by "full path" in Google Drive API v2

dear all
I'm trying to find a list of documents by "full path". And after reading the API reference, it seems to be a complex task. Assume my path is something like /path0/path1/path2/...
List children of root folder and find all children with name equals "path0" and put them to a list "result0"
Find all children of items in "result0" with name equals "path1" and put them to a list "result1"
Find all children of items in "result1" with name equals "path2" and ...
Above approach seems very low efficient cause it needs multiple interactions between my application and Drive. I understand Google Drive allows multiple files share the same file name even in the same folder. It will be handy if I can do something like:
listDocByFullPath("path0/path1/path2")
Is this possible with current version of Google Drive SDK? If it's not there yet, I was wondering if there is a simpler way than what I listed here.
BTW, as my application is purely a back-end service, it's not possible to use file picker provided by Google.
Cheers.
Unlike conventional file systems, a file could be under multiple folders on Drive. Folders are pretty much similar what labels are. Therefore, conventional paths dont always work within our abstraction. I'd suggest you to follow the logic below:
List files with q = 'root' in parents and title = 'path0' and mimeType = 'application/vnd.google-apps.folder' and pick the first result.
If there is a matching result, get the folder's id and perform another listing with '<id of path0>' in parents and title = 'path1' and mimeType='application/vnd.google-apps.folder' and pick the first result.
Keep going until you reach to your target folder.
The biggest problem is that a path does not uniquely identify the file or folder! For example, in the web UI, you can make 2 folders with the same name as children of the same folder.
i.e. you can make a tree that looks like:
root
|-somefolder
|-somefolder
Search / list with the param q set to name= and include fields param with "files(mimeType,id,name,parents)"
If there is only one search result, return this file
Else if there are multiple files, get the ID in parent and use file's get API with that ID and check if the name matches the last fragment in the path. If only one of the parent Ids match select that option else pick the matching parents and get to check the next parent element in the path
Essentially check bottom up
#Barcu Dogan is correct,, that's the only way to find full path, here is the implementation:
//BeanConfig.java
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public Drive drive() throws GeneralSecurityException, IOException {
Drive drive = new Drive.Builder(GoogleNetHttpTransport.newTrustedTransport(), JSON_FACTORY, getCredentials())
.setApplicationName(APPLICATION_NAME)
.build();
return drive;
}
private static HttpRequestInitializer getCredentials() throws IOException {
// Load client credentials from path
GoogleCredentials credential = GoogleCredentials.fromStream(new FileInputStream(CREDENTIALS_FILE_PATH))
.createScoped(DriveScopes.all());
return new HttpCredentialsAdapter(credential);
}
//FileHelper.java
//pass folderId or fileId
public String getCompletePath(Drive drive, String folderId) {
String path = "";
try {
File files = drive.files()
.get(folderId).setFields("id,name,parents")
.execute();
return recursivePath(drive, files, path);
} catch (IOException e) {
e.printStackTrace();
}
return path;
}
public String recursivePath(Drive drive, File currentFolder, String path) throws IOException {
if (currentFolder == null || currentFolder.getParents() == null || currentFolder.getParents().isEmpty())
return path;
if (!path.equalsIgnoreCase("")) {
path = currentFolder.getName() + "/" + path;
} else {
path = currentFolder.getName();
}
File parentFolder = drive.files().get(currentFolder.getParents().get(0)).setFields("id,name,parents").execute();
return recursivePath(drive, parentFolder, path);
}

How to set the modifiedDate on a google drive file in iOS AND prevent duplicate file names

I found the way in iOS (not obvious to me) to insert a file into a google drive folder using the standard queryForFilesInsertWithObject with uploadParameters and setting the folder id in a GTLDriveParentReference object, adding that object to an array, and assigning that array to the uploaded file's "parents" property. What I haven't yet figured out is how to overwrite any existing google drive file with a newer version without creating a duplicate. I would also like to stamp the new uploaded google drive file with the same date-time stamp as my local device file. When I try to use the file/update method and set the file.modifiedDate and query.setModifiedDate=YES, I always get a 400 Bad Request.
I can use patch to change the title just fine, but I can't set the modifiedDate using the api. I was able to change the modifiedDate using Google's Try it! APIs Explorer, so Google's servers are working fine. Here is the code that fails:
GTLDriveFile *file = [GTLDriveFile object];
NSString *fId = insertedFile.identifier;
file.title = #"Tony.jpg";
file.modifiedDate = myNewGoogleDate; // "2012-12-31T08:00:00+00:00"
GTLQueryDrive *qu = [GTLQueryDrive queryForFilesPatchWithObject:file fileId:fId];
qu.setModifiedDate = YES;
[service executeQuery:qu completionHandler:^(GTLServiceTicket *ticket,...
Take out the two lines with modifiedDate and the google-drive file title changes just fine. I traced into the api call to executeQuery and found that the following results as "DataToPost".
{"method":"drive.files.patch","id":"gtl_7","jsonrpc":"2.0",
"params": {"setModifiedDate":true,"resource":
{"modifiedDate":"2012-12-31T08:00:00+00:00",
"title":"Tony.jpg"},"fileId":"0BxRlXPR_hfR9OEJSdDdyQjkyaE0"},"apiVersion":"v2"}
Does anyone see "Bad Request" in this post? The error returned from the executeQuery is:
Error Domain=com.google.GTLJSONRPCErrorDomain Code=400 "The operation couldn’t be
completed. (Bad Request)" UserInfo=0x80724b0 {error=Bad Request,
GTLStructuredError=GTLErrorObject 0x806a7a0: {message:"Bad Request" code:400 data:[1]},
NSLocalizedFailureReason=(Bad Request)}
Does anyone have some code to set the modifiedDate that works?
You want to use queryForFilesUpdateWithObject instead to update the file using the original's fileId to ensure that the correct file is updated.
+ (id)queryForFilesUpdateWithObject:(GTLDriveFile *)object
fileId:(NSString *)fileId
uploadParameters:(GTLUploadParameters *)uploadParametersOrNil {
The duplicate file name is avoided if you supply the same pre-existing identifier (as probably explained in the answer above). The modifiedDate can be set using the GTLDateTime construction. Both solutions are shown below:
NSString *fileID = gF.identifier;
gF.modifiedDate = [GTLDateTime dateTimeWithDate:myNSDate timeZone:[NSTimeZone localTimeZone]];
if (fileID==0) query = [GTLQueryDrive queryForFilesInsertWithObject:gF uploadParameters:upParams];
else query = [GTLQueryDrive queryForFilesUpdateWithObject:gF fileId:fileID uploadParameters:upParams];
query.setModifiedDate = YES;
Below code is working with GoogleAPIClient v3 and i couldn't see any bad request.
driveFile = [GTLDriveFile object];
driveFile.modifiedTime = [GTLDateTime dateTimeWithDate:[NSDate date] timeZone:[NSTimeZone localTimeZone]];
output:
(lldb) po driveFile.modifiedTime
GTLDateTime 0x157a06470: {2016-08-10T13:44:33.609+05:30}