capacitor-community / http

Community plugin for native HTTP
MIT License
208 stars 135 forks source link

downloadFile method not working correctly on Android #266

Open tylerclark opened 2 years ago

tylerclark commented 2 years ago

Describe the bug I have an app which downloads text files from an API endpoint, stores them on the phone, and then uploads the same files into cloud storage bucket. Everything works great on web and iOS, but on Android I was getting IO Error. I launched Android Studio and studied the logs and discovered this error:

E/Capacitor/Plugin: IO Error
    java.io.FileNotFoundException: /storage/emulated/0/Documents/logs/JF-200804i^20220614-213606-0m_0kt_0s-shutdown.jfl: open failed: ENOENT (No such file or directory)
        at libcore.io.IoBridge.open(IoBridge.java:575)
        at java.io.FileOutputStream.<init>(FileOutputStream.java:236)
        at com.getcapacitor.plugin.http.HttpRequestHandler.downloadFile(HttpRequestHandler.java:438)
        at com.getcapacitor.plugin.http.Http.downloadFile(Http.java:167)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.getcapacitor.PluginHandle.invoke(PluginHandle.java:121)
        at com.getcapacitor.Bridge.lambda$callPluginMethod$0$Bridge(Bridge.java:598)
        at com.getcapacitor.-$$Lambda$Bridge$25SFHybyAQk7zS27hTVXh2p8tmw.run(Unknown Source:8)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loopOnce(Looper.java:226)
        at android.os.Looper.loop(Looper.java:313)
        at android.os.HandlerThread.run(HandlerThread.java:67)
     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:567)
        at libcore.io.BlockGuardOs.open(BlockGuardOs.java:273)
        at libcore.io.ForwardingOs.open(ForwardingOs.java:567)
        at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:8501)
        at libcore.io.IoBridge.open(IoBridge.java:561)
        at java.io.FileOutputStream.<init>(FileOutputStream.java:236) 
        at com.getcapacitor.plugin.http.HttpRequestHandler.downloadFile(HttpRequestHandler.java:438) 
        at com.getcapacitor.plugin.http.Http.downloadFile(Http.java:167) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.getcapacitor.PluginHandle.invoke(PluginHandle.java:121) 
        at com.getcapacitor.Bridge.lambda$callPluginMethod$0$Bridge(Bridge.java:598) 
        at com.getcapacitor.-$$Lambda$Bridge$25SFHybyAQk7zS27hTVXh2p8tmw.run(Unknown Source:8) 
        at android.os.Handler.handleCallback(Handler.java:938) 
        at android.os.Handler.dispatchMessage(Handler.java:99) 
        at android.os.Looper.loopOnce(Looper.java:226) 
        at android.os.Looper.loop(Looper.java:313) 
        at android.os.HandlerThread.run(HandlerThread.java:67) 

Now this error would seem to indicate that the log folder is not there but it is! I have it create it if it doesn't already exist long before this code runs. And it works perfect on iOS and web, just not Android. Weird. Ok so I changed my options from:

{
   url: this.localAddress + '/log/' + filename,
   method: 'GET',
   progress: true,
   filePath: '/logs/' + filename,
   directory: Directory.Data,
}

to

{
   url: this.localAddress + '/log/' + filename,
   method: 'GET',
   progress: true,
   filePath: filename,
   directory: Directory.Data,
}

All of a sudden it saves the file (kinda). I ended up having to write some custom code for Android only which is below:

if (this.isNative && this.isAndroid) {
   const tempPath = response.path.replace('storage/emulated/0/Documents/', '');
   const readFile = await Filesystem.readFile({
      path: tempPath,
      directory: Directory.Documents,
      encoding: Encoding.UTF8,
   });
   await Filesystem.writeFile({
      path: '/logs/' + this.device.name + '^' + filename,
      data: readFile.data,
      directory: Directory.Data,
      encoding: Encoding.UTF8,
   });
   await Filesystem.deleteFile({
      path: tempPath,
      directory: Directory.Documents,
   });
}

Notice how I have to reference the Directory.Documents instead of Directory.Data to actually read the file and then I'm basically creating a replica of the file in the right directory and deleting the original file.

To Reproduce

Expected behavior I believe the Android code is not aware of folders that actually exist. Even when doing a readDir I can see the log folder there as expected. This causes a compound set of issues which results in hacky code. I think the next problem is the Directory.Documents vs Directory.Data issue. Not sure why I am having trouble accessing the file correctly.

Smartphone: