Android DownloadManager remove() - how to determine when the remove() operation finishes? - android

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.

Related

How to know if removed File monitored by File Observer is a Directory or a File

I'm using FileObserver to monitor changes in folders.
Events are triggered as expected but I have problem making a distinction between Files and Directories in the events DELETE and MOVED_FROM, since after the event is triggered, calling both File.isFile() and File.isDirectory() is false (which make sense).
Is there an efficient way to make this check before file is removed? I do have a workaround by listing all files in effected folder, however it's inefficient.
Fileobserver code:
mFileObserver = new FileObserver(DIRECTORY.getPath()) {
#Override
public void onEvent(int event, String path) {
event &= FileObserver.ALL_EVENTS;
switch (event) {
case (CREATE):
case (MOVED_TO):
Log.d(TAG, "Added to folder: " + DIRECTORY + " --> File name " + path);
addChild(path);
break;
case (DELETE):
case (MOVED_FROM):
Log.d(TAG, "Removed from folder " + DIRECTORY + " --> File name " + path);
removeChild(path);
break;
case (MOVE_SELF):
case (DELETE_SELF):
removeDirectory();
break;
}
}
};
EDIT:
This is how is File/Folder is evaluated in removeChild(String)
private void removeChild(String name) {
mFileObserver.stopWatching();
String filepath = this.getAbsolutePath() + separator + name;
File file = new File(filepath);
if (file.exists())
Log.d(TAG, "Exists");
else Log.d(TAG, " Does not Exists");
if (file.isDirectory())
Log.d(TAG, "is Directory");
else Log.d(TAG, " is NOT Directory");
if (file.isFile())
Log.d(TAG, "is File");
else Log.d(TAG, " is NOT File");
}
And the relevant logcat output is:
04-03 12:37:20.714 5819-6352: Removed from folder /storage/emulated/0/Pictures/GR --> File name ic_alarm_white_24dp.png
04-03 12:37:20.714 5819-6352: Does not Exists
04-03 12:37:20.714 5819-6352: is NOT Directory
04-03 12:37:20.714 5819-6352: is NOT File
Is there an efficient way to make this check before file is removed?
Unfortunately, not that I'm aware of. Which makes sense - filesystem events are things that have already happened.
FileObserver uses inotify to get events. A good description of inotify functionality can be found at https://www.ibm.com/developerworks/library/l-inotify/:
Monitor Linux file system events with inotify
...
Before inotify there was dnotify. Unfortunately, dnotify had
limitations that left users hoping for something better. Some of the
advantages of inotify are:
Inotify uses a single file descriptor, while dnotify requires opening one file descriptor for each directory that you intend to
watch for changes. This can be very costly when you are monitoring
several directories at once, and you may even reach a per-process file
descriptor limit.
The file descriptor used by inotify is obtained using a system call and does not have an associated device or file. With dnotify, the file
descriptor pins the directory, preventing the backing device to be
unmounted, a particular problem with removable media. With inotify, a
watched file or directory on a file system that is unmounted generates
an event, and the watch is automatically removed.
Inotify can watch files or directories. Dnotify monitors directories, and so programmers had to keep stat structures or an
equivalent data structure reflecting the files in the directories
being watched, then compare those with the current state after an
event occurred in order to know what happened to the entry in the
directory.
As noted above, inotify uses a file descriptor, allowing programmers to use standard select or poll functions to watch for
events. This allows for efficient multiplexed I/O or integration with
Glib's mainloop. In contrast, dnotify uses signals, which programmers
often find more difficult or less than elegant. Signal-drive I.O
notification was also added to inotify in kernel 2.6.25.
The API for inotify
...
Note that there are no mentions of "events about to happen", or anything like that.
You don't need to keep a list of all files - you just need a list of directories - a simple Set<String> should do just fine. If the deleted String path is in the set, it was a directory.
For a more robust approach, when you start your watch you can also put a FileObserver watch on all directories in the directory you're primarily watching (also add a watch to every directory created in your primary directory after you've created your watcher).
Then if you get a DELETE_SELF from one of the child FileObserver objects, you'll know it was a directory. If the event doesn't have an associated child FileObserver object that got a DELETE_SELF event, it wasn't a directory.
For an extremely large directory, this approach admittedly will have scalability problems...

Android Storage permission WRITE ONLY?

I am really tired with debugging this problem. I have an application that writes to a storage. The application worked fine, until I got an update (I don't know which update caused it, because I had a break from Android development) - my Android version is 6.0.1 now.
Anyways I am trying to write to my external storage. I got prepared reading the new permission system for Android, got the permissions, and I am doing a check in my code:
checkSelfPermission("android.permission.READ_EXTERNAL_STORAGE")
checkSelfPermission("android.permission.WRITE_EXTERNAL_STORAGE")
By debugging I've checked (I also get a code that checks and asks if permission is necessary, but I wanted to be sure that it really is ;) ) I've observed that they return value 0, which is equal to PackageManager.PERMISSION_GRANTED
So, the permissions are granted for sure. I am trying to create an App directory in the storage to write pdf files, store sqlite DB and stuff like that.
Here is how I create the directory:
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
// We can read and write the media
File dir = Environment.getExternalStorageDirectory();
String sNewDir = Constants.ApplicationDataDir;
if (sSubDir == null) {
sSubDir = "";
} else {
sSubDir = sSubDir.trim();
}
if (sSubDir.length() > 0) {
sNewDir += File.separator + sSubDir;
}
File dirName = new File(dir, sNewDir);
if (!dirName.exists()) {
if (!dirName.mkdirs()) {
LogDump.e("getAppDirectory err", " (sSubDir=<" + sSubDir + "> cannot create dir: " + dirName.getAbsolutePath());
}
}
return dirName;
} else {
// Something else is wrong
LogDump.e("getAppDirectory err", " (sSubDir=<" + sSubDir + "> not mounted state: " + state);
return null;
}
The value of dirName is /storage/emulated/0/MY_APP_CONST_VALUE/sSubDir (the sSubdir is a parameter for subdirectory ie. "Preferences")
Soo, the code (checking with debugger) the execution checks that the directory does not exist, and the mkdirs() returns false result. THAT actually means that the directory does not exist, and could not be created.
During my application operation my logs get filled with:
W/FileUtils: Failed to chmod(/storage/emulated/0/MY_APP_CONST_VALUE/DB/pcdrdata.sqlite3): android.system.ErrnoException: chmod failed: EPERM (Operation not permitted)
from the SQLLite code
And FileNotFound exception for any other file operations...
This seems quite logical since It cannot create the directories, right?
Now for the most confusing part:
I am using total commander, and I can see that the directories are actually being created, the sqlite database is being copied, and all the files that do FileNotFoundException are there. I made a test, deleted them, rerun the App, and they are copied to the location once again. However any read operation on the files is not possible.
So it somehow creates the directories, allows to write (even thouh mkdirs() says otherwise) to them, but does not allow to read.
I am really confused, and tired of trying. I also tried the same without the SD card - identical results though.....
My application creates PDF files - they are created successful, but even the Total Commander says that it cannot open them....
My phone is Xperia Z3
As of KitKat you can't read from the root of the external storage anymore. You can only read in special public directories or in your own private directory on the sdcard. If you're seeing them created but not able to alter them, it seems like Android decided you could write there but not read there

Confused on how to access files from a ZipResourceFile (APK extension file)

My app is very image-heavy, so I need to supply expansion files with my APK. I've chosen to store all my images in a ZIP file and use the zip library to get access to my images.
My first intuition was to unzip all files when the app is first started and store them in the app's external directory. The guide gently nudges me to take a different approach:
Reading media files from a ZIP
If you're using your expansion files to store media files, a ZIP file
still allows you to use Android media playback calls that provide
offset and length controls (such as MediaPlayer.setDataSource() and
SoundPool.load()). In order for this to work, you must not perform
additional compression on the media files when creating the ZIP
packages.
So I guess I'll just get an input stream from the zip file when I have to, but I don't really know how long I should have that zip file open.
Suppose I have a gallery activity with a ViewPager that shows one image per page. Do I open my expansion zip file in onCreate and close it in onDestroy, or do I open and close the file for every new image loaded?
From API level 9, you could use the jobb tool to package your assets and use StorageManager to mount/dismount the OBB file. You can also use this tool to encrypt the assets, if need be.
OBBs are a good way of providing large amounts of binary assets without packaging them into APKs as they may be multiple gigabytes in size. However, due to their size, they're most likely stored in a shared storage pool accessible from all programs.
...
The OBB will remain mounted for as long as the StorageManager reference is held by the application. As soon as this reference is lost, the OBBs in use will be unmounted. The OnObbStateChangeListener registered with this call will receive the success or failure of this operation.
I guess a mounted OBB file can dismount at any unexpected time (for example, when the user turns on USB mass storage), so pay extra attention to your OnObbStateChangeListener.
Example from this question:
storage = (StorageManager) getSystemService( STORAGE_SERVICE );
storage.mountObb( obbFilepath, "optional_encryption_key", myListener );
You can use obbContentPath to read files just like they would be on disk.
private final OnObbStateChangeListener myListener = new OnObbStateChangeListener() {
#Override
public void onObbStateChange(String path, int state) {
super.onObbStateChange(path, state);
d(path + " changed to state " + state);
switch (state) {
case ERROR_ALREADY_MOUNTED:
case ERROR_COULD_NOT_MOUNT:
case ERROR_COULD_NOT_UNMOUNT:
case ERROR_INTERNAL:
case ERROR_NOT_MOUNTED:
case ERROR_PERMISSION_DENIED:
case UNMOUNTED:
//TODO
break;
case MOUNTED:
String assetsPath = mStorageManager.getMountedObbPath(mPathToObb);
if (assetsPath == null) throw new NullPointerException("Could not get path to mounted OBB path");
d("Checking if " + assetsPath + "/path/to/file exists");
File f = new File(assetsPath + "/path/to/file");
d("" + f.exists());
break;
default:
break;
}
}
};

Slashes removed from calls to File.mkdirs()

As the title suggests, I am trying to create a folder on Android, but all of the slashes have been removed from it.
For some more background information:
Specifically, I am trying to create a directory to store my application's users' files. These files must be accessible to the user from a file manager (such as File Manager HD) because the application does not support full file management. Using the standard from API level 8+, I reference the root of the publicly accessible folder with Environment.getExternalStoragePublicDirectory(). I then try to create a folder located at DCIM > Sketchbook > [the name of the sketch] using File.mkdirs(). For more information, see the code below.
I have already:
checked to make sure that the SD card is mounted, readable, and writable
enabled the permission WRITE_EXTERNAL_STORAGE
tried using File.mkdir() for every file in the hierarchy up to the folder location
tried using /, \\, File.separatorChar, and File.separator as folder separators
Code:
boolean success = true;
//The public directory
File publicDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
//The location of the sketchbook
File sketchbookLoc = new File(publicDir + "Sketchbook" + File.separator);
//The location of the sketch
//getGlobalState().getSketchName() returns the name of the sketch: "sketch"
File sketchLoc = new File(sketchbookLoc + getGlobalState().getSketchName() + File.separator);
if(!sketchLoc.mkdirs()) success = false;
//Notify the user of whether or not the sketch has been saved properly
if(success)
((TextView) findViewById(R.id.message)).setText(getResources().getText(R.string.sketch_saved));
else
((TextView) findViewById(R.id.message)).setText(getResources().getText(R.string.sketch_save_failure));
With various incarnations of the aforementioned tests (the ones that actually worked), I have received a consistent result: I get a new folder in DCIM whose name corresponds to the combination of all of the folders that should have been hierarchical parents of it. In other words, I have created a new directory, but all of the folder separators have been removed from it.
Now, I ask you:
Am I attempting to save the user data in the correct location? Is there another way that I should be doing this?
Is it even possible to create new folders in the DCIM folder? Does Android prevent it?
Is this problem specific to me? Is anyone else able to create a folder in the DCIM folder?
Am I using the right folder separators?
Is there something else that I am absolutely, completely, and utterly missing?
Now that I am done typing, and you are done reading my (excessively long) question, I hope that I can find some sort of answer. If you need clarification or more information, please say so.
EDIT: An example of the created folder is "DCIMSketchbooksketch", where it should be "DCIM/Sketchbook/sketch".
don't use
File sketchbookLoc = new File(publicDir + "Sketchbook" + File.separator);
but
File sketchbookLoc = new File(publicDir , "Sketchbook");
because publicDir.toString() will not end with a file separator (even if you declared it that way). toString() gives the canonical name of the file.
So your source becomes :
//The location of the sketchbook
File sketchbookLoc = new File(publicDir , "Sketchbook" );
//The location of the sketch
File sketchLoc = new File(sketchbookLoc , getGlobalState().getSketchName() );

Dropbox API Android

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
}

Categories

Resources