I would like to list the content of the content of a directory from Dropbox using its Android API.
I tried using dirEntry.contents() in a for loop, like this:
Entry rootDirEnt = mApi.metadata(mPath, 1000, null,
true, null);
if (!rootDirEnt.isDir || rootDirEnt.contents == null) {
// It's not a directory, or there's nothing in it
mErrorMsg = "No files available in Dropbox";
return false;
}
for (Entry childDirEnt : rootDirEnt.contents) {
// check if it still exists
if (childDirEnt.isDir && !childDirEnt.isDeleted
&& childDirEnt.contents != null) {
// childDirEnt contents is already null, even though there are files inside this directory
for (Entry fileEnt : childDirEnt.contents) {
// do smth with file
if (isCancelled()) {
return false;
} else {
publishProgress();
}
}
}
}
So I thought of usign mApi.metadata() again with the new path which works, but my question is: Can't I do it without calling medata() for every directory inside root dir ? (maybe using the contents call or smth.. )
You can call metadata again on each directory you find. Generally, Dropbox discourages calling metadata recursively like that unless driven by user action. See https://www.dropbox.com/developers/core/bestpractices.
You might instead use the delta API. The first time you call that, it will return a full list of all the content your app has access to in a user's Dropbox.
Related
I'm trying to crawl the entire file system of an android device, both directories and files, without the benefit of NIO, to build a tree of it. If I had NIO then I could use WalkTree or similar, but I don't.
The problem I am having (on the Nexus 5 API 23 x86 emulator) is in /sys/bus/pci/devices and possibly other directories (eg /proc/self) - it doesn't complete before the app times out/quits/crashes (unknown which), possibly getting into some kind of loop or something (the path may change in a repetitive fashion but the canonical path varies little or not at all) .
However if I rule out Symbolic links then that problem goes away but I get what is only some of the files on the device rather than all - for example lacking files under /data (or /data/media/0) and those files not showing up elsewhere - not to mention it looks completely different from the file system that most file managers show. The former is strange as I'd understood Symbolic Links pointed to files and folders that were still present in the file system, but just made them look as if they were elsewhere.
What's the solution? Do I have to code exceptions or special handling for /sys/bus/pci/devices, /proc/self and others? I'd prefer to keep Symbolic Links being followed if I can, and I'd prefer to crawl as many files and folders as I can (so starting in a sub-folder is not preferred).
And a few related questions that might affect the approach I eventually take - if I DO keep SymLinks then does that mean that some things will be crawled twice or more? Is there a way to avoid that? Is there a way to detect when something is the TARGET of a SymLink, other than following the SymLink and checking the CanonicalPath?
Here's my code:
I get the root (I understand that in Android, the first and likely only root is the valid one):
File[] roots = File.listRoots();
String rootPath = "";
try {
rootPath = roots[0].getCanonicalPath();
} catch (IOException e) {
// do something
}
Then I start the crawl (note the boolean to choose whether to ignore simlinks or not):
try {
// check if the rootPath is null or empty, and then...
File rootFile = new File(rootPath);
rootNode = new FileFolderNode(rootFile, null, true, false); // last param may be true to ignore sim links
//FileFolderNode(String filePath, FileFolderNode parent, boolean addChildren, boolean ignoreSimLinks)
} catch (Exception e) {
// do something
}
That uses the FileFolderNode, which has constructor:
public FileFolderNode(File file, FileFolderNode parent, boolean addChildren, boolean ignoreSimLinks) throws IOException {
if (file == null)
throw new IOException("File is null in new FileFolderNode");
if (!file.exists())
throw new IOException("File '" + file.getName() + "' does not exist in new FileFolderNode");
// for now this uses isSymLink() from https://svn.apache.org/repos/asf/commons/_moved_to_git/io/trunk/src/main/java/org/apache/commons/io/FileUtils.java adjusted a bit to remove Java 7 and Windows mentions
if (!ignoreSimLinks)
if (FileUtils.isSymlink(file))
return;
this.name = file.getName();
if (this.name.equals("") && ! file.getCanonicalPath().equals("/"))
throw new IOException("Name is empty in new FileFolderNode");
this.isDirectory = file.isDirectory();
if (this.isDirectory) {
this.children = new ArrayList<FileFolderNode>();
if (addChildren) {
File[] files = file.listFiles();
if (files == null) {
// do something
} else {
// add in children
for (File f : files) {
FileFolderNode child = null;
try {
child = new FileFolderNode(f, this, addChildren, ignoreSimLinks);
} catch (Exception e) {
child = null;
}
if (child != null)
children.add(child);
}
}
}
}
}
Given the lack of answers here, I've broken this question down into areas needing clarification, and am trying to get answers to those - please do see if you can help with those:
Get Android Filing System root
Android SymLinks to hidden or separate locations or partitions
Avoiding Android Symbolic Link loop
android.os.FileObserver requires a java.io.File to function.
But with Android 10 Google restricted access to everything but your app's private directory due to the famous "Storage Access Framework". Thus, accessing anything via java.io.File breaks and renders FileObserver useless unless you intend to use it in your app's private directory. However, I want to be notified when something is changed in a certain directory on external storage. I would also like to avoid periodically checking for changes.
I tried using ContentResolver.registerContentObserver(uri,notifyForDescendants,observer) and ran into some problems with that method:
Every Uri I have plugged in so far was accepted
It neither fails nor notifies if the Uri doesn't work
I cannot find any documentation telling me which Uris actually work
The only thing I got working to some extent is the following approach:
// works, but returns all changes to the external storage
contentResolver.registerContentObserver(MediaStore.Files.getContentUri("external"), true, contentObserver)
Unfortunately this includes all of the external storage and only returns Media Uris when changes happen - for example content://media/external/file/67226.
Is there a way to find out whether or not that Uri points to my directory?
Or is there a way to make registerContentObserver() work with a Uri in such a way that I get a notification whenever something in the folder has changed?
I also had no success trying various Uris related to DocumentsFile and external storage Uris.
I kept getting errors when even trying to use the base constructor such as the following -
No direct method <init>(Ljava/util/List;I)V in class Landroid/os/FileObserver; or its super classes (declaration of 'android.os.FileObserver' appears in /system/framework/framework.jar!classes2.dex)
From a comment on Detect file change using FileObserver on Android:
I saw that message (or something like that) when i was trying to use constructor FileObserver(File). Use of deprecated FileObserver(String) solved my problem.... Original FileObserver has bugs.
Full disclosure, I was using the Xamarin.Android API; however, the gist and the commenter I quoted were both working with Java. At any rate, indeed - tried again using the counterpart String constructor and I was finally able to make and use the observer. Grinds my gears to use a deprecated API, but apparently they're hanging onto it at least up to and including Android 12.0.0_r3... still, would much prefer the supported constructors actually work. Maybe there's some warrant here for filing an issue.
I found a way to implement FileObserver on Android 10 with ContentObserver, but it might only work with media files since it works with media content uris.
The uri for ContentResolver.registerContentObserver() should be the file's corresponding media uri (e.g. content://media/external/file/49) which is queried by file path.
fun getMediaUri(context: Context, file: File): Uri? {
val externalUri = MediaStore.Files.getContentUri("external")
context.contentResolver.query(
externalUri,
null,
"${MediaStore.Files.FileColumns.DATA} = ?",
arrayOf(file.path),
null
)?.use { cursor ->
if (cursor.moveToFirst()) {
val idIndex = cursor.getColumnIndex("_id")
val id = cursor.getLong(idIndex)
return Uri.withAppendedPath(externalUri, "$id")
}
}
return null
}
Then ContentObserver.onChange() will be triggered for every file change with uri: content://media/external/file/{id}; uri in ContentObserver.onChange(boolean selfChange, Uri uri) will always be content://media/external/file; only registered file will be with id (e.g. content://media/external/file/49?deletedata=false).
Does what FileObserver used to do when input uri's path matches registered uri.
I have a temporary solution for this issue, so let's see if this can help.
I start an infinite while loop watching for file created and file deleted (if you want file modified or file renamed you have to implement more) using DocumentFile. Below is my sample:
private static int currentFileIndirectory = 0;
private static final int FILE_CREATED = 0;
private static final int FILE_DELETED = 1;
private static DocumentFile[] onDirectoryChanged(DocumentFile[] documentFiles, int event) {
Log.d("FileUtil", "onDirectoryChanged: " + event);
if (event == FILE_CREATED) {
} else {
}
return documentFiles;
}
private static boolean didStartWatching = false;
private static void startWatchingDirectory(final DocumentFile directory) {
if (!didStartWatching) {
didStartWatching = true;
DocumentFile[] documentFiles = directory.listFiles();
if (null != documentFiles && documentFiles.length > 0) {
currentFileIndirectory = documentFiles.length;
}
new Thread(new Runnable() {
public void run() {
while (true) {
DocumentFile[] documentFiles = directory.listFiles();
if (null != documentFiles && documentFiles.length > 0) {
if (documentFiles.length != currentFileIndirectory) {
if (documentFiles.length > currentFileIndirectory) {//file created
DocumentFile[] newFiles = new DocumentFile[documentFiles.length - currentFileIndirectory];
onDirectoryChanged(newFiles, FILE_CREATED);
} else {//file Deleted
onDirectoryChanged(null, FILE_DELETED);
}
currentFileIndirectory = documentFiles.length;
}
}
}
}
}).start();
}
}
I need to list all images in a users drive cloud.
I use https://www.googleapis.com/drive/v2/files with the correct filter to query all images. I need to group the result in my app in folders. I know a file can have multiple parents, that's fine.
I would like to avoid making and calls (for every single file a single call) to get a files folder via https://developers.google.com/drive/v2/reference/files/get using the parent id from the first call.
Is there a network friendly way to get all files inclusive there folders?
EDIT
A simple solution would be to get all folders with ids in one query and lookup folder names in this result. Maybe that's somehow possible?
As you answered yourself in the comments above (but you can't match names, you have to match IDs; names aren't unique).
Step 1: get all your folders in one shot (paging results, filtering unneeded fields, skipping the trashed ones):
private static Drive mGOOSvc;
....
static ArrayList<ContentValues> getFolders() {
ArrayList<ContentValues> cvs = new ArrayList<>();
if (mGOOSvc != null) try {
Drive.Files.List qry = mGOOSvc.files().list()
.setQ("mimeType = 'application/vnd.google-apps.folder'")
.setFields("items(id,labels/trashed,parents/id,title),nextPageToken");
String npTok = null;
if (qry != null) do {
FileList gLst = qry.execute();
if (gLst != null) {
for (File gFl : gLst.getItems()) {
if (gFl.getLabels().getTrashed()) continue;
for (ParentReference parent : gFl.getParents())
cvs.add(newContentValues(gFl.getTitle(), gFl.getId(), parent.getId()));
}
npTok = gLst.getNextPageToken();
qry.setPageToken(npTok);
}
} while (npTok != null && npTok.length() > 0);
} catch (Exception e) { /* handle Exceptions */ }
return cvs;
}
Step 2: Parse the resulting ArrayList to build the tree structure (match ParentIds, handle multiple parents)
Step 3: Do the same for files with mime type ""image/jpeg", "image/png", ... "whatever img mimetype" (just modify the code above to get files) and parse again.
Of course the 'execute()' method will produce exceptions that should be handled as pointed out here.
... and you can take the 'not so network friendly' approach of iterating down the folder tree as seen in the 'testTree()' method here. Recursion is necessary if you have no knowledge how deep your tree structure is.
Good Luck
The process seemed quite simplistic at first, but there must be something that I am missing going forward with this task. There was a settings file that I wanted to create local to my application for storing a whole bunch of data (not preference worthy). I ended up saving the file with the following code snippet.
protected File createSettingsFileLocation(String fileNameF)
{
File directoryFile = context_.getDir("settings", Context.MODE_PRIVATE);
File settingsFile;
settingsFile = new File(directoryFile, fileNameF);
if (!settingsFile.exists())
{
try
{
settingsFile.createNewFile();
} catch(IOException e)
{
Log.e(MyConstants.LOG_TAG, "Could not create the file as intended within internal storage.");
return null;
}
}
return settingsFile;
}
and then proceeded to retrieve the file later by looking for it locally with the following code snippets.
public String getCurrentFileContainingSettings()
{
List<String >settingFilesInFolder = getLocalStorageFileNames();
if (settingFilesInFolder == null || settingFilesInFolder.isEmpty())
{
return null;
}
String pathToCurrentSettingsFile = settingFilesInFolder.get(0);
return pathToCurrentSettingsFile;
}
protected List<String> getLocalStorageFileNames()
{
return Arrays.asList(context_.fileList());
}
However, the settingFilesInFolder always returns no entries, so I get null back from the getCurrentFileContainingSettings(). As what I could see from the documentation it seems as thought I was doing it right. But, I must be missing something, so I was hoping that someone could point something out to me. I could potentially hard-code the file name once it has been created within the system in a preference file for access later the first time that the settings are created, but I shouldn't have to do something like that I would think.
fileList() only looks in getFilesDir(), not in its subdirectories, such as the one you created via getDir(). Use standard Java file I/O (e.g., list()) instead.
I have an app that loads an URL. My problem is that it shows me the old version of that webpage and never loads the new one(I think it keeps the webpage in cache and loads it from there even though I have wireless connection). Is there a way to programmatically specify my app to never keep the webpages in memory? Or maybe I have a different problem that need another approach.
try something like this(not tested) for deleting the cache at the end of session:
private int clearCacheFolder(){
deletedFiles = 0;
File dir = context.getCacheDir();
if (dir!= null && dir.isDirectory()) {
try {
for (File child:dir.listFiles()) {
//delete subderictories
if (child.delete()) {
deletedFiles++;
}
}
}
}
catch(Exception e) {
}
}
return deletedFiles;
}
or
setAppCacheEnabled(false);
for disabling it
//cache is per-application, so this will clear the cache for all WebViews used.
clearCache(boolean includeDiskFiles)
// prob not what you want
clearFormData()
// prob not what you want
clearHistory()