gualtierofrigerio / WKDownloadHelper

SPM with an helper to download files from a WKWebView
MIT License
20 stars 12 forks source link

Error Domain=NSURLErrorDomain Code=-3000 #3

Closed peschee closed 2 years ago

peschee commented 2 years ago

Hi

I found your article about downloads in a WKWebView to be very valuable. I have followed your example and am unable to download files. I'm testing with the latest 15.x iOS. The error I always get in the download method of WKDownloadDelegate is

Error Domain=NSURLErrorDomain Code=-3000 "(null)"

The error code -3000 seems to be NSURLErrorCannotCreateFile (https://developer.apple.com/documentation/foundation/1508628-url_loading_system_error_codes/nsurlerrorcannotcreatefile/) which in turns suggests that there's an issue with creating the file. From what I can tell, this is all handled inside the "new" WKWebView.

Did you ever run into such an issue? Is there a permission that needs to be enabled in Info.plist for this to work?

Documentation seems to be pretty scarce :/

gualtierofrigerio commented 2 years ago

Hi,

you mean you always receive this error inside download(_ download: WKDownload, didFailWithError error: Error, resumeData: Data?)? Do you get it on a device or the simulator?

peschee commented 2 years ago

Hi

Thanks for getting back to me. Yes, I am getting this error inside func download(_ download: WKDownload, didFailWithError error: Error, resumeData: Data?), I never get into the successful callback func downloadDidFinish(_ download: WKDownload).

I have only tested inside the Simulator, and to be fair, my "mock server" is running locally using http:// (no SSL). I will re-test in a device and using SSL as well.

gualtierofrigerio commented 2 years ago

I just tried on a 15.2 simulator and it worked, I opened google on the WKWebView, looked for an xls file and downloaded it from a server. I don't think I ever tried with a local server. What happens if you navigate to a page on your http server inside a WKWebView?

peschee commented 2 years ago

Could you try something on your end: can you try to download the same file twice?

I'm currently testing on a device over SSL, and it works each time a file is downloaded for the first time. As soon as I try to do it again for the same file (even after restarting the app), it fails with:

Error Domain=NSURLErrorDomain Code=-3000 "(null)"

When I prepend a filestamp to the suggested filename, it works each time as well…

Something like this:

   public func download(_ download: WKDownload, decideDestinationUsing response: URLResponse, suggestedFilename: String, completionHandler: @escaping (URL?) -> Void) {
        let temporaryDir = NSTemporaryDirectory()

        let date = Date()
        let format = DateFormatter()
        format.dateFormat = "yyyy-MM-dd-HH-mm-ss"
        let timestamp = format.string(from: date)

        let fileName = temporaryDir + "/" + timestamp + suggestedFilename
        let url = URL(fileURLWithPath: fileName)
        fileDestinationURL = url
        completionHandler(url)
    }

What happens if you navigate to a page on your http server inside a WKWebView?

This works, so I'm unsure whether it really is about HTTP vs HTTPS.

EDIT: Ok, downloading from the local server works as well… The (permission?) issue really seems to be related to the filename being reused…

gualtierofrigerio commented 2 years ago

Oh sure it is the same file downloaded twice I think I always tested different files. I'll update a fix later, I may prepend a timestamp as you suggest or have a way to delete the file once it's been copied elsewhere, to avoid having too much stuff in the temporary directory.

peschee commented 2 years ago

I just checked with your example project, and it is the same issue there.

Prepending the timestamp is not really nice, since it changes the filename. I'm also not sure whether it is about the file already being in the temporary directory. The error message also really doesn't give you much, thanks 🍎 :)

What I am using in my implementation (I had to implement myself because I have other navigationDelegate logic I need to handle and I wasn't able to subclass your library) to fix issues with double slashes inside filenames // is

let fileName = NSString.path(withComponents: [temporaryDir, suggestedFilename])

instead of

let fileName = temporaryDir + "/" + suggestedFilename

peschee commented 2 years ago

If I add this:

        do {
            if (FileManager.default.isDeletableFile(atPath: fileName)) {
                try FileManager.default.removeItem(atPath: fileName)
            }
        } catch {
            print("Cannot delete temporary file ", fileName)
        }

it works! So removing the temporary file with the same name seems to fix the issue.

gualtierofrigerio commented 2 years ago

I just released a new tag. Every time a new file is downloaded a new random directory is created and the file is put inside it. This way even if you try to download two files with the same name using two instances of the class, you should be able to to it. Maybe it is overkill, and your proposal is working as well. I'll think about something better, like deleting the file after is being shared, but in the meantime my solution and yours are an acceptable workaround.

peschee commented 2 years ago

Thanks, that's wonderful!

I had to implement it with a custom delegate anyway, since I needed additional logic inside

func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void)

So I'm only able to re-use parts of your library, but your article and the library helped me a long way implementing the downloads!

It's a shame Apple did not provide something like this (their implementation on iOS Safari)

IMG_3ED0E00A48C1-1

for the WKWebView as well :)

gualtierofrigerio commented 2 years ago

To improve my library I can add another function to WKDownloadHelperDelegate where I ask for the path of the file given the suggested name, so whoever uses my package can implement the logic to remove an existing file, appending or prepending the timestamp as you initially suggested, or move the new file to a different directory etc. The default implementation could use the temporary directory. Will work on that tomorrow it should be quick to add.

gualtierofrigerio commented 2 years ago

Tag 0.3.0 introduces a new function in the delegate that allows you to specify the URL of the file, the default implementation puts it into a random directory in tmp.