Google Drive iOS SDK - google-drive-api

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

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"));
}
});

How to open local file from browser?

I'm using the following when trying to open a local file:
some document
When I click the above in a browser, it opens Finder to the folder. But does not open the file. Should I be doing something else to have the file open in Numbers?
You cannot open local files on the client. This would be a huge security risk.
You can link to files on your server (like you did) or you can ask the client for a file using <input type="file">
You can only open some types of files in browsers, like html css js and mp4, otherwise the browser will want to download it. Also remember that browsers replace spaces with %20. I recommend right clicking the file and opening it with chrome then copy that link and using it.
You can open files that are local as long as it is a file that is on the file that is trying to open another file is local.
Your issue is likely the space in the document name. Try this instead:
some document
The %20 will be read by your browser as a space.
Update
The other answer points out something I missed. The .numbers extension will not be able to be opened directly by your browser. Additionally the other answer describes the security risk this could create.
The File API in HTML 5 now allows you to work with local files directly from JS (after basic user interaction in selecting the file(s), for security).
From the Mozilla File API docs:
"The File interface provides information about files and allows JavaScript in a web page to access their content.
File objects are generally retrieved from a FileList object returned as a result of a user selecting files using the <input> element, from a drag and drop operation's DataTransfer object, or from the mozGetAsFile() API on an HTMLCanvasElement."
For more info and code examples, see the sample demo linked from the same article.
This might not be what you're trying to do, but someone out there may find it helpful:
If you want to share a link (by email for example) to a network file you can do so like this:
file:///Volumes/SomeNetworkFolder/Path/To/file.html
This however also requires that the recipient connects to the network folder in finder --- in menu bar,
Go > Connect to Server
enter server address (e.g. file.yourdomain.com - "SomeNetworkFolder" will be inside this directory) and click Connect. Now the link above should work.
Here is the alternative way to download local file by client side and server side effort:
<a onclick='fileClick(this)' href="file://C:/path/to/file/file.html"/>
js:
function fileClick(a) {
var linkTag = a.href;
var substring = "file:///";
if (linkTag.includes(substring)) {
var url = '/v/downloadLocalfile?path=' +
encodeURIComponent(linkTag);
fileOpen(url);
}
else {
window.open(linkTag, '_blank');
}
}
function fileOpen(url) {
$.ajax({
url: url,
complete: function (jqxhr, txt_status) {
console.log("Complete: [ " + txt_status + " ] " + jqxhr);
if (txt_status == 'success') {
window.open(url, '_self');
}
else {
alert("File not found[404]!");
}
// }
}
});
}
Server side[java]:
#GetMapping("/v/downloadLocalfile")
public void downloadLocalfile(#RequestParam String path, HttpServletResponse
response) throws IOException, JRException {
try {
String nPath = path.replace("file:///", "").trim();
File file = new File(nPath);
String fileName = file.getName();
response.setHeader("Content-Disposition", "attachment;filename=" +
fileName);
if (file.exists()) {
FileInputStream in = new FileInputStream(file);
response.setStatus(200);
OutputStream out = response.getOutputStream();
byte[] buffer = new byte[1024];
int numBytesRead;
while ((numBytesRead = in.read(buffer)) > 0) {
out.write(buffer, 0, numBytesRead);
}
// out.flush();
in.close();
out.close();
}
else {
response.setStatus(404);
}
} catch (Exception ex) {
logger.error(ex.getLocalizedMessage());
}
return;
}
You can expose your entire file system in your browser by using an http server.
caddy2 server
caddy file-server --listen :2022 --browse --root /
serves the root file system at http://localhost:2022/
python3 built-in server
python3 -m http.server
serves current dir on http://localhost:8000/
python2 built-in server
python3 -m SimpleHTTPServer
serves current dir on http://localhost:8000/
This s

Google Drive Api (V2) `corpora` choices

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.

Upload a file to Google Drive with embedded browser c#

Since I am unable to capture browser window close event using the GoogleWebAuthorizationBroker.AuthorizeAsync API, I followed this link (http://www.daimto.com/google-api-and-oath2/) to create an embedded browser and authenticate the user. I am unable to continue further to use the access token to upload a file in google drive. Is there any example available to continue from the above link to upload/download a file from Google Drive.
Regards,
Amrut
From the same author, there is a documentation how to upload/ download files to Google Drive.
Like with most of the Google APIs you need to be authenticated in order to connect to them. To do that you must first register your application on Google Developer console. Under APIs be sure to enable the Google Drive API and Google Drive SDK, as always don’t forget to add a product name and email address on the consent screen form.
Make sure your project is at least set to .net 4.0.
Add the following NuGet Package
PM> Install-Package Google.Apis.Drive.v2
In order to download a file we need to know its file resorce the only way to get the file id is from the Files.List() command we used earlier.
public static Boolean downloadFile(DriveService _service, File _fileResource, string _saveTo)
{
if (!String.IsNullOrEmpty(_fileResource.DownloadUrl))
{
try
{
var x = _service.HttpClient.GetByteArrayAsync(_fileResource.DownloadUrl );
byte[] arrBytes = x.Result;
System.IO.File.WriteAllBytes(_saveTo, arrBytes);
return true;
}
catch (Exception e)
{
Console.WriteLine("An error occurred: " + e.Message);
return false;
}
}
else
{
// The file doesn't have any content stored on Drive.
return false;
}
}
Using _service.HttpClient.GetByteArrayAsync we can pass it the download url of the file we would like to download. Once the file is download its a simple matter of wright the file to the disk.
Remember from creating a directory in order to upload a file you have to be able to tell Google what its mime-type is. I have a little method here that try’s to figure that out. Just send it the file name. Note: When uploading a file to Google Drive if the name of the file is the same name as a file that is already there. Google Drive just uploads it anyway, the file that was there is not updated you just end up with two files with the same name. It only checks based on the fileId not based upon the file name. If you want to Update a file you need to use the Update command we will check that later.
public static File uploadFile(DriveService _service, string _uploadFile, string _parent) {
if (System.IO.File.Exists(_uploadFile))
{
File body = new File();
body.Title = System.IO.Path.GetFileName(_uploadFile);
body.Description = "File uploaded by Diamto Drive Sample";
body.MimeType = GetMimeType(_uploadFile);
body.Parents = new List() { new ParentReference() { Id = _parent } };
// File's content.
byte[] byteArray = System.IO.File.ReadAllBytes(_uploadFile);
System.IO.MemoryStream stream = new System.IO.MemoryStream(byteArray);
try
{
FilesResource.InsertMediaUpload request = _service.Files.Insert(body, stream, GetMimeType(_uploadFile));
request.Upload();
return request.ResponseBody;
}
catch (Exception e)
{
Console.WriteLine("An error occurred: " + e.Message);
return null;
}
}
else {
Console.WriteLine("File does not exist: " + _uploadFile);
return null;
}
}

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);
}