I'm running a query on the Android SDK for Google Drive to check if a directory with a specific name exists or creating it otherwise (directory name is the resource title on Google Drive).
The problem I'm having with the following code is that it never finds my folder and creates a new one every time and I'm not sure why. It successfully finds the directory if the SDK created it itself.
public static final String FOLDER_NAME_CORE = "My Core Folder";
MetadataBuffer meta = Drive.DriveApi.query(mGoogleApiClient, new Query.Builder()
.addFilter(Filters.eq(SearchableField.TRASHED, false))
.addFilter(Filters.eq(SearchableField.TITLE, FOLDER_NAME_CORE ))
.setSortOrder(new SortOrder.Builder().addSortDescending(SortableField.MODIFIED_DATE).build())
.build()).await().getMetadataBuffer();
if (metadataBufferResult.getCount() > 0) {
Log.d(TAG, "Creating new folder");
...
} else {
Log.d(TAG, "Using existing folder");
}
I've tried making the folder publicly shared but it didn't change anything (as expected). Does anyone know what I have to change to make it find the existing folder instead? As far as I know this list is the only possible search options.
I hope you are aware of the supported scopes.
I think there are 2 things that cause your problem and you will have to re-think your app's logic.
First, GDAA (unlike the REST Api), introduces some latency, so the file/folder may not exist on the Drive for awhile, even if you got your DriveId (see this).
Second, the fact that you use TITLE as an indicator of existence does not work in the GooDrive universe, since TITLE is not unique (you already know that).
I would recommend this approach:
when your (GDAA based) app creates a file/folder, wait until you get it's ResourceId. That confirms it's existence (again here).
always check for the existence of a file/folder in the Drive using it's ResourceId (turned into DriveId)
Related
I'm using Xamarin, C# and Monogame and I'm taking a fully-working Desktop game and porting it over to Android.
My problem is that I have this "Content folder" that you would always use in the Desktop version of the app. But I cannot access it or any other folder through the code directly using Android.
basicShader = new Effect(game1.GraphicsDevice,System.IO.File.ReadAllBytes("Content/TextureShader.mgfxo"));
This works just fine in the Desktop app but throws System.IO.DirectoryNotFoundException:'Could not find a part of the path "/Content/TextureShader.mgfxo".' on Android.
I'd like to mention that I already had the code and the project working perfectly when it was a desktop program. I also have a private class-level variable string[] list_of_files and in the constructor, I had the line list_of_files = Directory.GetFiles("./Content","*.txt");
This is for saving and loading player data. It may have been rudimentary but I had a fully functioning program that saved and loaded data on my computer. I am transitioning this program to be an Android app and this is the only part of the project that isn't working. When I run the code as it was originally written, I get "System.IO.DirectoryNotFoundException: 'Could not find a part of the path '/Content'.' ".
I've tried playing around with trying to read the contents of different folders.
I've messed around with different paths, including the Resources folder instead.
I added <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> to my manifest.
I know that I'm trying to access internal storage, not external, so I also tried <uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" /> just to see if that might work.
Nothing works.
In another stack overflow post, a guy commented:
For the people who are facing NullPointerException - you are trying to access the files in the app's internal storage which are private sorry you can't do that. –
coderpc
Jun 23, 2017 at 16:00
I cannot imagine why this would be true. Why would a programmer not be able to write a program that can access it's own internal storage? That makes no sense to me. Obviously my app needs to be able to read and write it's own internal storage! And if this is true, then how else can I save persistent data on my phone? I don't want a database or a shared thingamabobber that uses key-value pairs, I have a self-made system that works as a text file and I want to continue to use it. I refuse to believe that an Android app can't keep track of a simple .txt file in one of it's own folders, that's just too hard for me to imagine. It can't be true.
I wanted to ask the commenter about his comment but Stack Overflow wouldn't let me because I don't have over 50xp.
Just like CommonsWare sayed, you can use the Intent.ActionOpenDocument to get the uri of the file. Such as
static readonly int READ_REQUEST_CODE = 1337;
Intent intent = new Intent(Intent.ActionOpenDocument);
intent.AddCategory(Intent.CategoryOpenable);
intent.SetType("*/*");
StartActivityForResult(intent, READ_REQUEST_CODE);
And override the OnActivityResult method:
if (requestCode == READ_REQUEST_CODE && resultCode == Result.Ok)
{
// The document selected by the user won't be returned in the intent.
// Instead, a URI to that document will be contained in the return intent
// provided to this method as a parameter. Pull that uri using "resultData.getData()"
if (data != null)
{
Android.Net.Uri uri = data.Data;
DocumentFile documentFile = DocumentFile.FromSingleUri(this.ApplicationContext,uri);
// Then you can operate the file with input and output stream
}
}
More information please check the simple on the github:
https://github.com/xamarin/monodroid-samples/blob/main/StorageClient/StorageClientFragment.cs
In addition, if you can ensure the file's path. You can use the StreamWriter and the StreamReader to write and read the file. Such as:
using (StreamWriter sw = new StreamWriter(path))
{
sw.WriteLine(content);
}
Furthermore, you can try to create the content folder and the txt file in the Android with the following code.
var filename1 = Android.App.Application.Context.GetExternalFilesDir(System.DateTime.Now.ToString("Content")).AbsolutePath;
var filename = System.IO.Path.Combine(filename1, "xxx.txt");
using (System.IO.FileStream os = new System.IO.FileStream(filename, System.IO.FileMode.Create))
{
}
The folder and the files created by this way belongs to the app and you can access it easily.
You can read the official document about the storage in the Android.
Link : https://developer.android.com/training/data-storage/shared/documents-files
I am introducing in Google Drive Android Api as docs and examples show.
I created two activities which extend BaseDemoActivity of the example: the first one adds empty files to Drive customizing on each file some CustomProperties, the second one lists from Drive the files added grabbing the owned CustomProperties of each file.
first activity - code which adds files like this:
DriveFolder folder = Drive.DriveApi.getFolder(getGoogleApiClient(),
mFolderDriveId);
CustomPropertyKey customPropertyKeyExample = new CustomPropertyKey(
"custom", CustomPropertyKey.PRIVATE);
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setTitle("New empty file")
.setMimeType("text/plain")
.setCustomProperty(customPropertyKeyExample, "xyz")
.build();
folder.createFile(getGoogleApiClient(), changeSet, null)
.setResultCallback(fileCallback);
second activity - code which reads properties like this:
for (Iterator<Metadata> i = result.getMetadataBuffer().iterator(); i
.hasNext();) {
Metadata mChildren = ((Metadata) i.next());
if (!mChildren.isTrashed()) {
Map<CustomPropertyKey, String> mapProperties = mChildren
.getCustomProperties();
if (mapProperties.get(customPropertyKeyExample) == null)
// THIS TEST RETURNS TRUE UNTIL DRIVE SYNC EXECUTES
}
}
}
Them work, but i notice that the second activity, the list activity, must wait a Drive variable sync time to have the CustomProperties available.
Is there a way to get the CustomProperties from an activity immediately after them added by a different activity?
This isn't expected behavior. Custom file properties should be available locally without performing a sync.
I've created a bug on our issue tracker to discuss this further:
https://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=3848
Could you please respond on the bug, answering the following question:
Can you verify that the title is also changed immediately?
Which example class specifically are you using for your first and second activity?
How are you sharing the DriveId for the folder between the activities?
Are you using DriveFolder#listChildren or another query to get [result] in the second example?
I am using Google Play Services SDK to integrate Google Drive.
My application need to show a list of all files in a folder.
From the demos: ListFilesInFolderActivity
I get the folder properly without any Authorization errors etc.. Because I changed the
com.google.android.gms.drive.sample.demo.BaseDemoActivity.EXISTING_FOLDER_ID
com.google.android.gms.drive.sample.demo.BaseDemoActivity.EXISTING_FILE_ID
values as per my application folder and file etc.
But in the
final private ResultCallback<MetadataBufferResult> metadataResult = new
ResultCallback<MetadataBufferResult>() {
#Override
public void onResult(MetadataBufferResult result) {
if (!result.getStatus().isSuccess()) {
showMessage("Problem while retrieving files");
return;
}
mResultsAdapter.clear();
mResultsAdapter.append(result.getMetadataBuffer());
showMessage("Successfully listed files.");
}
};
I only see the Toast "Successfully listed files." but nothing in the List. When checked for mResultsAdapter.getCount() it returns 0.
But the folder definitely has 1 file. What am I missing?
EDIT :
When I created folders/files from my application, they are visible. But folders/files added from web etc.. are not visible in the List.
Is it something like:
Only folders/files created by your application are accessible?
using
folder.listChildren(getGoogleApiClient()).setResultCallbackmetadataResult);
The Android API uses Drive.File scope, which means your app will only be able to see the files that the user has explicitly authorized your app to access. (Users much prefer this scope, since they have more control over who can see what data.)
If you open items with the same app on the web, they should be accessible to your Android app, but there may be some small delay in the showing up in the list response.
I have folders on my Google Drive account and i want to add files in specific folder. How can I do it? In example needed DriveId (EXISTING_FOLDER_ID), but I don't know DriveID my folder, I know name only.
#smokybob. I don't know if it helps with the original question, but per your comment:
Here's a code snippet that should get you folder/file DriveId. The 'title', 'mime' and 'fldr' arguments are optional. In case you pass null for 'fldr', the search is global (within the app's FILE scope), otherwise within the specified folder (not in subfolders, though). It uses the simplest - 'await()' flavor, that has to be run off the UI thread.
Be careful though, the folder / file names are not unique entities in the Google Drive Universe. You can have multiple files / folders with the same name in a single folder !
GoogleApiClent _gac; // initialized elsewhere
//find files, folders. ANY 'null' ARGUMENT VALUE MEANS 'any'
public void findAll(String title, String mime, DriveFolder fldr) {
ArrayList<Filter> fltrs = new ArrayList<Filter>();
fltrs.add(Filters.eq(SearchableField.TRASHED, false));
if (title != null) fltrs.add(Filters.eq(SearchableField.TITLE, title));
if (mime != null) fltrs.add(Filters.eq(SearchableField.MIME_TYPE, mime));
Query qry = new Query.Builder().addFilter(Filters.and(fltrs)).build();
MetadataBufferResult rslt = (fldr == null) ?
Drive.DriveApi.query(_gac, qry).await() :
fldr.queryChildren(_gac, qry).await();
if (rslt.getStatus().isSuccess()) {
MetadataBuffer mdb = null;
try {
mdb = rslt.getMetadataBuffer();
if (mdb != null) {
for (Metadata md : mdb) {
if (md == null) continue;
DriveId dId = md.getDriveId(); // here is the "Drive ID"
String title = md.getTitle();
String mime = md.getMimeType();
// ...
}
}
} finally { if (mdb != null) mdb.close(); }
}
}
In general terms, you can get the 'DriveId' you need from:
file / folder name as shown in the code above, or
the string identifier you've got from 'encodeToString()'. I use it for caching MYROOT folder id, for instance, or
the string identifier you've got from 'getResourceId()'. This is the string you see in the html address. But since your only scope is FILE, don't count on using it to open something your app did not create.
Both 2 and 3 identifiers are strings, so they may be confused. Identifier 2 is faster when retrieving Drive ID (via decodeFromString()). Identifier 3 is slower to retrieve (via fetchDriveId()), but usefull if you need to take your ID elsewhere (Apps Script, for instance). See also SO 21800257
As far as creation of files / folders is concerned, I have some code on GitHub here. If you look at awaits\MainActivity.java ... buildTree(), you will see the creation of folders / files recursively when building a simple folder tree.
OK. I experienced the same issue with the Drive demos on Github: https://github.com/googledrive/android-demos
At the bottom of the readme, it says:
If you actually want to run this sample app (though it is mostly
provided so you can read the code), you will need to register an OAuth
2.0 client for the package com.google.android.gms.drive.sample.demo with your own debug keys and set any resource IDs to those that you
have access to. Resource ID definitions are on:
com.google.android.gms.drive.sample.demo.BaseDemoActivity.EXISTING_FOLDER_ID
com.google.android.gms.drive.sample.demo.BaseDemoActivity.EXISTING_FILE_ID
They didn't really specify how exactly one should get and set the resourceID for the EXISTING_FOLDER_ID and EXISTING_FILE_ID constants defined in the BaseDemoActivity file and after reading seanpj comment above, I used his method number 3 to find the DriveIDs so that I can add it to the demo app so that it runs properly.
All one needs to do is to go to Drive on your PC then click on the "link" button at the top.
A portion of the link says "id=XXXXXXXXXXX". Copy that and paste it into the BaseDemoActivity file in the demo app. Do this for both a file and a folder that you create yourself on Drive.
The Demo app should now run successfully.
you can use query to find folder
Query query = new Query.Builder().addFilter(Filters.and(
Filters.eq(SearchableField.TITLE, "folder name"),
Filters.eq(SearchableField.TRASHED, false))).build();
I fount one sample tutorial
http://wiki.workassis.com/android-google-drive-api-deleted-folder-still-exists-in-query/
in this tutorial they are calling asynchronously
If you have the metadata for the folder (or a DriveFolder object, from which you can get the metadata), you can just call getDriveId.
If you have the web resource, you can get the DriveId using that resource id using fetchDriveId
I have yet another hurdle to climb with my GOOGLE DRIVE SDK Android App. I am uploading scanned images with tightly controlled index fields - user defined 'tags' from local dictionary. For instance XXX.JPG has index words "car" + "insurance". Here is a simplified code snippet:
...
body.setTitle("XXX.JPG");
body.setDescription("car, insurance");
body.setIndexableText(new IndexableText().setText("car insurance"));
body.setMimeType("image/jpeg");
body.setParents(Arrays.asList(new ParentReference().setId(...)));
FileContent cont = new FileContent("image/jpeg", new java.io.File(fullPath("xxx.jpg")));
File gooFl = _svc.files().insert(body, cont).execute();
...
Again, everything works great, except when I start a search, I get results that apparently come from some OCR post process, thus rendering my system's DICTIONARY unusable. I assume I can use a custom MIME type, but then the JPEG images become invisible for users who use standard GOOGLE DRIVE application (local, browser-based ... ). So the question is: Can I upload MIME "image/jpeg" files with custom indexes (either Indexable, or Description fields) but stop GOOGLE from OCR-ing my files and adding indexes I did not intend to have?
Just to be more specific, I search for "car insurance" and instead of my 3 files I indexed this way, I get unmanageable pile of other results (JPEG scanned documents) that had "car" and "insurance" somewhere in them. Not what my app wants.
Thank you in advance, sean
...
Based on Burcu's advise below, I modified my code to something that looks like this (stripped to bare bones):
// define meta-data
File body = new File();
body.setTitle("xxx.jpg");
body.setDescription(tags);
body.setIndexableText(new IndexableText().setText(tags));
body.setMimeType("image/jpeg");
body.setParents(Arrays.asList(new ParentReference().setId(_ymID)));
body.setModifiedDate(DateTime.parseRfc3339(ymdGOO));
FileContent cont =
new FileContent("image/jpeg",new java.io.File(fullPath("xxx.jpg")));
String sID = findOnGOO(driveSvc, body.getTitle());
// file not found on gooDrive, upload and fix the date
if (sID == null) {
driveSvc.files().insert(body, cont).setOcr(false).execute();
driveSvc.files().patch(gooFl.getId(), body).setOcr(false).setSetModifiedDate(true).execute();
// file found on gooDrive - modify metadata and/or body
} else {
// modify content + metadata
if (contentModified) {
driveSvc.files().update(sID, body, cont).setOcr(false).setSetModifiedDate(true).execute();
// only metadata (tags,...)
} else {
driveSvc.files().patch(sID, body).setOcr(false).setSetModifiedDate(true).execute();
}
}
...
It is a block that uploads or modifies a Google Drive file. The two non-standard operations are:
1/ resetting the file's 'modified' date in order to force the date of file creation - tested, works OK
2/ stopping the OCR process that interferes with my apps indexing scheme - will test shortly and update here
For the sake of simplicity, I did not include the implementation of "findInGOO()" method. It is quite simple 2-liner and I can supply it upon request
sean
On insertion, set the ocr parameter to false:
service.files().update(body, content).setOcr(false).execute();