Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

NullPointerException When Downloading File #378

Open
3 tasks done
Jack-Drake opened this issue Jun 27, 2024 · 5 comments
Open
3 tasks done

NullPointerException When Downloading File #378

Jack-Drake opened this issue Jun 27, 2024 · 5 comments
Labels

Comments

@Jack-Drake
Copy link

Bug Report

Problem

What is expected to happen?

The file is downloaded and stored onto the device.

What does actually happen?

An error showing that the file name is null and throws null pointer exception

Information

{"code":3,"source":"https:\/\/sample.io\/images\/logo.png","target":"https:\/\/localhost\/__cdvfile_persistent__\/imgcache\/04a568427e026c9d4dc0275d9ba360c7a24326be.png","http_status":200,"exception":"java.lang.NullPointerException"} java.lang.NullPointerException at java.io.FileOutputStream.<init>(FileOutputStream.java:227) at java.io.FileOutputStream.<init>(FileOutputStream.java:186) at org.apache.cordova.filetransfer.FileTransfer$2.run(FileTransfer.java:796) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:923)

Command or Code

`var fileTransfer = new Private.FileTransferWrapper(ImgCache.attributes.filesystem);
fileTransfer.download(
img_src,
filePath,
function (entry) {
entry.getMetadata(function (metadata) {
if (metadata && ('size' in metadata)) {
ImgCache.overridables.log('Cached file size: ' + metadata.size, LOG_LEVEL_INFO);
Private.setCurrentSize(ImgCache.getCurrentSize() + parseInt(metadata.size, 10));
} else {
ImgCache.overridables.log('No metadata size property available', LOG_LEVEL_INFO);
}
});
ImgCache.overridables.log('Download complete: ' + Helpers.EntryGetPath(entry), LOG_LEVEL_INFO);

    // iOS: the file should not be backed up in iCloud
    // new from cordova 1.8 only
    if (entry.setMetadata) {
      entry.setMetadata(
        function () {
          /* success*/
          ImgCache.overridables.log('com.apple.MobileBackup metadata set', LOG_LEVEL_INFO);
        },
        function () {
          /* failure */
          ImgCache.overridables.log('com.apple.MobileBackup metadata could not be set', LOG_LEVEL_WARNING);
        },
        {
          // 1=NO backup oddly enough..
          'com.apple.MobileBackup': 1
        }
      );
    }

    if (success_callback) {
      success_callback(entry.toURL());
    }
  },
  function (error) {
    if (error.source) { ImgCache.overridables.log('Download error source: ' + error.source, LOG_LEVEL_ERROR); }
    if (error.target) { ImgCache.overridables.log('Download error target: ' + error.target, LOG_LEVEL_ERROR); }
    ImgCache.overridables.log('Download error code: ' + error.code, LOG_LEVEL_ERROR);
    if (error_callback) { error_callback(error); }
  },
  on_progress
);`

Environment, Platform, Device

Android@13.0
Android Tablet

Version information

Android Studio: Latest version as of this posting
Cordova Plugins:
cordova-plugin-device 3.0.0 "Device"
cordova-plugin-file-transfer 2.0.0 "File Transfer"
cordova-plugin-file 8.1.0 "File"

Checklist

  • I searched for existing GitHub issues
  • I updated all Cordova tooling to most recent version
  • I included all the necessary information above
@breautek
Copy link
Contributor

Analysis at a glance

Based on the stacktrace, it's failing at https://github.com/apache/cordova-plugin-file-transfer/blob/master/src/android/FileTransfer.java#L796 which is when it's trying to create the output stream for the downloaded file to local disk.

the file object must be null because the mapUriToFile call above must have returned null, which it does if the download path is not a supported protocol. It should be a file:// path, e.g. cordova.file.dataDirectory + /myFile.

It would be helpful if you can confirm and provide the value used for the local file path in the download API usage.

Workaround

Assuming that what I think above is correct, it's caused by your app passing in a https:// url as the local download file path. This should be a file:// path instead. It looks like you're passing in: https://localhost/__cdvfile_persistent__/imgcache/... which likely is coming from a fileEntry.toURL() somewheres within the codebase. The file docs does state this is the recommended API to use, but it's not supported by the download API since CordovaResourceApi doesn't actually support mapping that URL back to a file:// url.

The workaround would be using fileEntry.nativeURL instead which should be local file:// path.

I'm still going to tag this as a bug, the CordovaResourceApi could probably be updated to convert https://localhost/__cdvfile_persistent__/... endpoints back to a file:// uri so that .toURL() is supported, but further analysis would have to be done to determine if there any other side effects in doing so.

@breautek breautek added the bug label Jun 27, 2024
@Jack-Drake
Copy link
Author

I have changed the filePath to file:// instead of https://. I have received error 1.
{"code":1,"source":"https:\/\/sample.io\/images\/logo.png","target":"file:\/\/localhost\/__cdvfile_files__\/imgcache\/04a568427e026c9d4dc0275d9ba360c7a24326be.png","http_status":200,"exception":"\/__cdvfile_files__\/imgcache\/04a568427e026c9d4dc0275d9ba360c7a24326be.png: open failed: ENOENT (No such file or directory)"} java.io.FileNotFoundException: /__cdvfile_files__/imgcache/04a568427e026c9d4dc0275d9ba360c7a24326be.png: open failed: ENOENT (No such file or directory) at libcore.io.IoBridge.open(IoBridge.java:492) at java.io.FileOutputStream.<init>(FileOutputStream.java:236) at java.io.FileOutputStream.<init>(FileOutputStream.java:186) at org.apache.cordova.filetransfer.FileTransfer$2.run(FileTransfer.java:796) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:923) Caused by: android.system.ErrnoException: open failed: ENOENT (No such file or directory) at libcore.io.Linux.open(Native Method) at libcore.io.ForwardingOs.open(ForwardingOs.java:166) at libcore.io.BlockGuardOs.open(BlockGuardOs.java:254) at libcore.io.ForwardingOs.open(ForwardingOs.java:166) at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:7550) at libcore.io.IoBridge.open(IoBridge.java:478) at java.io.FileOutputStream.<init>(FileOutputStream.java:236)  at java.io.FileOutputStream.<init>(FileOutputStream.java:186)  at org.apache.cordova.filetransfer.FileTransfer$2.run(FileTransfer.java:796)  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)  at java.lang.Thread.run(Thread.java:923) 

@breautek
Copy link
Contributor

file://localhost/__cdvfile_files__/imgcache/04a568427e026c9d4dc0275d9ba360c7a24326be.png

This path doesn't make sense, the directory must exists and /localhost/__cdvfile_files__/imgcache is not a valid path in android.

That's why I suggested to use the cordova.file.dataDirectory constant earlier, but since it looks like you're wanting a cache, cordova.file.cacheDirectory could also be a valid choice. You can see here for more constants.

The constants provides the base path for your platform. On android it will look something like /data/data/<your app id>/cache/ (some devices uses /data/user/0/... instead of /data/data/...) for the cache directory. On iOS the same constant will be different but will point to the cache directory on iOS. As long as you store only relative paths, the code base will work cross platform.

Additionally if you use a subdirectories, then you'll also have to make sure the directories exists first, and if not create them before attempting to download the file.

@Jack-Drake
Copy link
Author

Just for context i am using a Library called ImgCache in order to download and save images on an android device:
ImgCache JS

It uses the file transfer plugin to save the file and this is a picture of the implementation of the function that should save the picture
image

I have attempted to replace the https:// to file:// and it returns the error that I sent in the last message (error 1)

This is the output of the console.log that is seen in the picture above:
https://sample.io/images/logo.png https://localhost/__cdvfile_files__/imgcache/04a568427e026c9d4dc0275d9ba360c7a24326be.png file://localhost/__cdvfile_files__/imgcache/04a568427e026c9d4dc0275d9ba360c7a24326be.png

@breautek
Copy link
Contributor

I see.

I glanced over that library, it's obvious it was built some time ago. It uses toURL() behind the scenes.

There's some history with that API and with the cdvfile:// protocol.

In older versions of Cordova, where the webview was loaded over the file:// protocol, cordova handled a special protocol called cdvfile://, which allowed you to reference file resources on disk that is outside of the app's asset container inside webview features (like img tags for example). .toURL() intent is to give a URL usable in the webview to load the asset. During this time, toURL() also returned a file:// path to the resource.

Nowadays, there's a lot more restrictions in the name of privacy/security. Cordova uses a newer API called WebViewAssetLoader. This is what maps https://localhost calls to a cordova-implemented webview client. Paths are only resolvable through the webview. Using the asset loader, the file:// and other custom protocols (like cdvfile://) are blocked. This means the .toURL() usage and intent was broken, so to work around that, we map a specific URL endpoint.

But that means .toURL() is returning a https:// path instead of a file:// path, so even though the docs once even suggested using .toURL() for file transfer plugin, today that doesn't actually work. In part because the purpose of .toURL() was overloaded.

I admit this is kind of messy, but for modern platforms the library should probably use .nativeURL instead of .toURL() on all file entries that interactions with the file transfer plugin, which should be guaranteed to be a file:// representation of the file path.

Simply replacing the https:// part of the string that .toURL() returns will not provide a proper file:// path since it's a virtual path handled by the web view asset loader, which is something external to the file transfer plugin.

If the original author is not around for that image cache library, it may need to be forked and updated so that it supports the current versions of the cordova plugins, if their license permits so.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants