downloadToFile help

Hi All,

I am trying to use the downloadToFile function within URL to help create a download manager. For some reason I can’t seem to get it to download a file. I have tried to see if it is returning an error but I’m just getting bad access crashes when I call the hadError() function. I assume the DownloadTask is being mopped by the URL because it finishes the scope of what it is doing (whatever it is doing it is not downloading the file for whatever reason). I’ve seen things on the forum about people using async stuff to get this function to work but if its not even downloading anyway, I haven’t seen the point in doing so yet. Please see some simplified test code below, I’d love it if someone could point out where I am going wrong.

downloadButton.onClick = [this] {
    juce::URL download("https://upload.wikimedia.org/wikipedia/commons/thumb/6/6b/JUCE_Logo.png/220px-JUCE_Logo.png");
    std::unique_ptr<juce::URL::DownloadTask> taskProgress = download.downloadToFile(juce::File::getSpecialLocation(juce::File::userDesktopDirectory));
    bool error = taskProgress->hadError();
};

Thanks!

Well, a quick look at the JUCE code seems to indicate that there are failures to starting the process that will return a nullptr, ie. if this check fails. if (auto outputStream = targetFileToUse.createOutputStream (bufferSize))) And since you are not checking for that, it is likely your crash is happening from accessing a nullptr.

Now, having said that, to solve your actual problem, you will still need to understand why it is failing. For which I would encourage you to step into the JUCE code with a debugger. :slight_smile:

1 Like

Taken a look at doing some debug stuff.

I have now edited code so it’s a lambda being called asynchronously by the message manager. I am actually getting a file created now as I have specified a file path to write to. It is still unfortunately generating an empty file.

void MainComponent::downloadFile()
{
    juce::URL download("https://upload.wikimedia.org/wikipedia/commons/thumb/6/6b/JUCE_Logo.png/220px-JUCE_Logo.png");
    std::unique_ptr<juce::URL::DownloadTask> taskProgress = download.downloadToFile(juce::File::getSpecialLocation(juce::File::userDesktopDirectory).getChildFile("fileToWriteTo.png");
    bool error = taskProgress->hadError();
}

Though placing some breakpoints I have traced my issued through to the run() function within URL. It never enters this while loop because threadShouldExit() is true:

while (! (stream->isExhausted() || stream->isError() || threadShouldExit()))
    {
        if (listener != nullptr)
            listener->progress (this, downloaded, contentLength);

        auto max = (int) jmin ((int64) bufferSize, contentLength < 0 ? std::numeric_limits<int64>::max()
                                                                     : static_cast<int64> (contentLength - downloaded));

        auto actual = stream->read (buffer.get(), max);

        if (actual < 0 || threadShouldExit() || stream->isError())
            break;

        if (! fileStream->write (buffer.get(), static_cast<size_t> (actual)))
        {
            error = true;
            break;
        }

        downloaded += actual;

        if (downloaded == contentLength)
            break;
    }

I have never used the Thread class before so if anyone has any ideas why this could be, I’d be very grateful!

Also, I must be doing something wrong with the initial writeToFile as I’m sure this is getting far more complicated than it should be!?

Looks like you already figured out the issue with the output file name, it indeed has to be a full file path, not just the destination directory.

Another issue is that you will have to wait for the download operation to finish like in the following code I just tested :

juce::URL download("https://upload.wikimedia.org/wikipedia/commons/thumb/6/6b/JUCE_Logo.png/220px-JUCE_Logo.png");
    File destFile = juce::File::getSpecialLocation(juce::File::userDesktopDirectory).getChildFile("juce_logo.png");
    destFile.deleteFile();
    std::unique_ptr<juce::URL::DownloadTask> taskProgress = download.downloadToFile(destFile);
    if (taskProgress)
    {
        while (taskProgress->isFinished() == false)
        {
            std::cout << "downloading...\n";
            Thread::sleep(500);
        }
        bool error = taskProgress->hadError();
        if (error == false)
        {
            std::cout << "downloaded ok\n";
        }
        else
            std::cout << "download failed\n";

    }
    else
        std::cout << "downloadtask could not be created\n";

This is of course not ideal to be done like this in the GUI thread, because it will block updates and user interaction while the download is going on. So you will need to figure out some kind of async handling for this. (A new thread is not required, the download already uses a thread internally. I suppose you could do something with a GUI timer that checks periodically if the download has finished.)

In case it’s helpful, here’s a simplified version of a class that I’ve used for downloading files in the past:

struct Downloader   : juce::URL::DownloadTask::Listener
{
    explicit Downloader (const juce::URL& downloadURL)
        : url (downloadURL)
    {
    }

    void downloadToFile (const juce::File& destinationFile)
    {
        task = url.downloadToFile (destinationFile, {}, this);
    }

    void finished (juce::URL::DownloadTask*, bool success) override
    {
        if (onFinish)
            onFinish();
    }

    std::function<void()> onFinish;

private:
    juce::URL url;
    std::unique_ptr<juce::URL::DownloadTask> task;
};

// Example usage:
Downloader d (juce::URL ("https://en.wikipedia.org/wiki/Bat_flight"));
d.downloadToFile (juce::File::getSpecialLocation (juce::File::userDesktopDirectory).getChildFile ("DownloadTest.html"));
d.onFinish = []() { DBG ("Download finished"); };
2 Likes

Thanks for this. The bit that made it work was the “Juce::thread::sleep(500)”. Without it I was ending up with an empty file. For the sake of learning, do you have any idea why that might be?

Amazing, thanks fo this.

The download is an async task that happens in another thread. You have to somehow let it run to the end. Note that the while() loop in the code I posted is essential for that kind of use, you can’t just put a single 500 millisecond sleep in the code, since the download may require more time to finish than that. In any case :

The code given above by connorreviere looks like the correct approach to handle it in a GUI application situation.

1 Like

The code that connorreviere gave had the same issue as mine. It wasn’t downloading the file - just saying zero bytes. I altered it with the Thread::sleep() function and it is launched on a different thread with Thread::launch();

I’ve now successfully got it downloading from my server and have download progress bars in my GUI.

Thanks, for your help.

That’s strange – were you by chance using it in a way that the Downloader object was being destroyed before the download had completed?

1 Like

Ahh yes, you’re right - I was… Thanks for your help!