This question would be related mostly to Android, however it is a little more general.
I can upload and download files using getFile and such API calls, however, I am trying to access a directory which exists already on Dropbox and list it's content.
Using the API, I would like to be able to reference the directory at path '/path/to/dir/' and list all the Entry objects inside it using Entry.contents.
I've tried this and it doesn't work:
Entry dir = new Entry();
dir.path = "/path/to/dir";
Log.d(TAG, "isDir ? " + Boolean.toString(dir.isDir));
for (Entry entry : dir.contents) {
Log.d(TAG, entry.filename());
}
The directory is not empty. isDir returns false and no filenames are printed.
Is there a way to do this, or do I need to use the search method?
All I want is the list of files in an existing directory.
Thanks a lot
This can be done with the metadata function:
String path2Dir = "path/to/dir";
Entry dir = mDropboxApiInstance.metadata(path2Dir, null, true, null);
List<Entry> contents = dir.contents;
for (Entry dropboxFile : contents){
// do stuff with remote file's data
}
Related
I want to copy a single json file from Unity's Assets/Resources/ directory to the internal persistent memory of Android device (INTERNAL STORAGE/Android/data/<packagename>/files/) only once at the start of the game. I looked up all over the internet, with solutions saying to use Streaming assets and UnityWebRequests or WWW class which is obsolete now. I tried everything for hours now. And did not find a exact solutions, but only got null references and directory not found.
I added my json file to the <UnityProject>/Assets/Resources/StreamingAssets/, but it doesn't seem to detect any streaming assets. I understand an apk is a zip file and so I can only read the contents of streaming assets, but my I can't find a solution to this.
Once I get my json from streaming asset, I can finally add it to Application.persistentDataPath.
UPDATE
I had actually figured it out,
public void LoadFromResources() {
{mobilePath = "StreamingAssets/"; mobilePath = mobilePath + "stickerJson" + ".json";
string newPath = mobilePath.Replace(".json", "");
TextAsset ta = Resources.Load<TextAsset>(newPath);
string json = ta.text; Debug.Log(json); System.IO.File.WriteAllText(Application.persistentDataPath + "/stickers.json", json);
}
}
To copy a file you only need to have the information of the file and then create a new one in another place.
So if you got your json file into your Resources directory you can retreive like the documentation say:
//Load text from a JSON file (Assets/Resources/Text/jsonFile01.json)
var jsonTextFile = Resources.Load<TextAsset>("Text/jsonFile01");
//Then use JsonUtility.FromJson<T>() to deserialize jsonTextFile into an object
Then you only have to create your android file in your persistentDataPath as #Iggy says in the comments doing:
System.IO.File.WriteAllText(Path.Combine(Application.persistentDataPath, "file.txt"), jsonTextFile);
Note: Not really sure, but for other cases I think that StreamingAssets folder should be inside Assets, no inside Resources.
I have a list of arrays of data in my app that I would now like to write to a file (csv) and use a 3rd party app (such as email) to share this csv file. I have had no luck finding any helpful resources for creating, finding the file path for, and appending to a file in Kotlin. Does anyone have experience with this or have examples to point to? Just to get started I'm trying to write the header and close the file so I can see that it is correctly writing.
This is what I have for now:
val HEADER = "ID, time, PSI1, PSI2, PSI3, speed1, speed2, temp1, temp2"
val filename = "export.csv"
var fileOutStream : FileOutputStream = openFileOutput(filename,Context.MODE_PRIVATE)
try {
fileOutStream.write(HEADER.toByteArray())
fileOutStream.close()
}catch(e: Exception){
Log.i("TAG", e.toString())
}
It doesn't throw the exception, but I cannot find the file in the file system. I'm using a physical tablet for testing/debug. I've checked the com.... folder for my app.
I cannot find the file in the file system
Use Android Studio's Device File Explorer and look in /data/data/.../files/, where ... is your application ID.
Also, you can write your code a bit more concisely as:
try {
PrintWriter(openFileOutput(filename,Context.MODE_PRIVATE)).use {
it.println(HEADER)
}
} catch(e: Exception) {
Log.e("TAG", e.toString())
}
use() will automatically close the PrintWriter, and PrintWriter gives you a more natural API for writing out text.
It appears there are many ways to create a file and append to it, depending on the minimum API version you are developing for. I am using minimum Android API 22. The code to create/append a file is below:
val HEADER = "DATE,NAME,AMOUNT_DUE,AMOUNT_PAID"
var filename = "export.csv"
var path = getExternalFilesDir(null) //get file directory for this package
//(Android/data/.../files | ... is your app package)
//create fileOut object
var fileOut = File(path, filename)
//delete any file object with path and filename that already exists
fileOut.delete()
//create a new file
fileOut.createNewFile()
//append the header and a newline
fileOut.appendText(HEADER)
fileOut.appendText("\n")
/*
write other data to file
*/
openFileOutput() creates a private file, likely inside of app storage. These files are not browsable by default. If you want to create a file that can be browsed to, you'll need the WRITE_EXTERNAL_STORAGE permission, and will want to create files into a directory such as is provided by getExternalFilesDir()
Is there an easy mechanism to determine when a DownloadManager remove() has completed, as it appears to be partially asynchronous. The function returns almost instantaneously with a count of the entries in the Download table it's deleted, but the actual filesystem housekeeping appears to be pushed into some background thread.
Issue is I've written a little bit of code that looks for and deletes any existing DownloadManager entry for file X (and hopefully the filesystem object), before pulling a new copy. Unfortunately the new copy is making it to the directory before the filesystem housekeeping has kicked in, for the previous incarnation. So the housekeeping actually ends up deleting the new version at some point and leaving an orphan entry in the DownloadManager table.
Could do with some way to block till the filesystem delete is actioned.
Debug code:
DownloadManager.Query query = new DownloadManager.Query().setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL);
downloads = getAllDownloadIds(manager.query(query));
path = activity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
//If a file name was passed remove any existing entry / version of the file
if ( fileName != null && ! fileName.isEmpty() ){
if ( downloads.containsKey(fileName)){
ids = downloads.get(fileName);
for (Long id : ids) {
Uri path = manager.getUriForDownloadedFile(id);
File checkFile = new File(path.toString());
Log.e(TAG, "Removing existing file: " + path.toString() + ":" + id);
int entriesRemoved = manager.remove(id);
Log.e(TAG, "Existing files removed: " + entriesRemoved);
}
}
}
...
Log.v(TAG, "Attempting to create a file in the 'Download' directory on the external storage::" + path.toString() +"/"+ fileName);
file = new File(path, fileName);
Log.v(TAG, "Does the file already exist::" + file.exists());
Example output:
… V/Export﹕ Removing existing file: file:///storage/sdcard/Download/appData.csv:101
… V/Export﹕ Existing files removed: 1
… V/Export﹕ Attempting to create a file in the 'Download' directory on the external storage::/storage/sdcard/Download/appData.csv
… V/Export﹕ Does the file already exist::true
I had this same problem - when replacing small files in a fast network, the replacement files would sometimes arrive within a fraction of a second after calling DownloadManager.remove(...) In this case, the newly arrived files would be deleted.
The solution I'm using is, before I call DownloadManager.remove(...), I setup up a FileObserver to monitor the file. I then call remove(...), but then wait for the DELETE event to fire before initiating the replacement download.
This ended being a significant amount of code distributed among multiple classes. There are other complicating factors - for example I put a timeout mechanism in just in case the DownloadManager never deletes the file. (I can't imagine why it wouldn't, but it's not my component).
So in answer to the question "Is there an easy mechanism.....": There are mechanisms available to you, but unfortunately not particularly easy ones.
The way i'm solved this timing problem is just to delete the file before DownloadManager.remove function. The reference to the download will be removed by the "remove" function anyway.
int idFromCursor = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_ID));
String localPathOfFile =cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
File fileToDelete= new File(localPathOfFile);
if(fileToDelete.exists()){
fileToDelete.delete();
}
downloadManager.remove(idFromCursor);
after that there was no timing problems anymore.
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
all you guys who know things I don't :-)
I've run into this problem that may not be actually a problem, only a revelation that I don't know what I'm doing. AGAIN!
I'm uploading a JPEG with some description and indexable keywords. Works like a charm. But I can't figure out how to add/modify meta data later, without creating another instance of the file. So, when I add a picture of my dog with description "dog", I end up with what I wanted. But if I try to modify the metadata by either using:
gooFl = drvSvc.files().insert(meta).execute();
or
gooFl = drvSvc.files().insert(meta,null).execute();
I end up with a new file (of the same name) on GOOGLE Drive.
See the code snippet below:
File meta = new File();
meta.setTitle("PicOfMyDog.jpg");
meta.setMimeType("image/jpeg");
meta.setParents(Arrays.asList(new ParentReference().setId(ymID)));
File gooFl = null;
if (bNewJPG == true) {
meta.setDescription("dog");
meta.setIndexableText(new IndexableText().setText("dog"));
gooFl = drvSvc.files().insert(meta,
new FileContent("image/jpeg", new java.io.File(fullPath("PicOfMyDog.jpg"))))
.execute();
} else {
meta.setDescription("dick");
meta.setIndexableText(new IndexableText().setText("dick"));
// gooFl = drvSvc.files().insert(meta).execute();
gooFl = drvSvc.files().insert(meta,null).execute();
}
if (gooFl != null)
Log.d("atn", "success " + gooFl.getTitle());
It is the "else" branch I'm asking about. First file one has meatadata "dog", second "dick".
So, what's the solution. Do I delete the previous instance (and how)? Is there another syntax / method I don't know about?
thank you, sean
If you need to modify the metadata, use files.patch.
drvSvc.files().patch(id, meta).execute();
In cases you need both modify the metadata and the file contents, use files.update.
drvSvc.files().update(id, meta, content).execute();
Insertions make POST requests that always create a new resource.
You need to use Files.Patch if you want to update only Metadata. files().insert always creates a new file.
A full list of File commands and what operations you need to use can be found in the API Reference