Heya!
What you're asking for is not simple, but it can be done
It comes down to three problems:
1. Normally, http.request doesn't give you any updates about the download progress; you just call it and it returns when the download is done (or when it ran into an error). What you need is a way to get updated on the download progress while the download is happening. For this, you can write a custom sink. The sink that you pass to http.request receives the downloaded data in chunks. Normally these chunks would then be written to a file. By writing a custom sink, you can run some code whenever a new chunk of data arrives. A sink is just a function that accepts two arguments: a chunk of data, and possibly an error message.
Here is a sink that prints the size of each chunk whenever it receives a new chunk:
Code: Select all
local function progress_sink(chunk, err)
if chunk then
print(string.format("received %d bytes", #chunk))
elseif err then
print(err)
else
print("Download finished")
end
end
To use this sink, pass it to the http.request function:
Code: Select all
local request = {
url = "https://love2d.org/style/screenshots/oh-my-giraffe.png",
method = "GET",
sink = custom_sink,
}
local success, status_code = http.request(request)
When you run this, you will get a lot of prints saying "received X bytes". That's a step in the right direction.
However, now the downloaded file is not saved anywhere. To change that, we can combine our custom sink with one of the standard sinks that are included in the lua-socket library:
Code: Select all
-- this function creates a sink that forwards the downloaded chunk to another sink
local function progress_sink(output_sink)
-- this function is the actual sink
return function(chunk, err)
if chunk then
print(string.format("received %d bytes", #chunk))
else
-- if chunk is nil, then the download is finished, or we have an error
if err then
print(err)
else
print("Download finished")
end
end
-- forward chunk and err to the underlying sink
return output_sink(chunk, err)
end
end
-- this is the sink that will write the downloaded data to a file
local output_sink = ltn12.sink.file(io.open("test.png", "wb"))
local request = {
url = url,
sink = progress_sink(output_sink),
method = "GET",
}
local success, status_code = http.request(request)
Now, while the file is downloading, our progress sink will keep us updated on the number of bytes that have been downloaded.
However, there is still another problem: the http.request function still blocks while the download is running. This means that you won't be able to draw anything to the screen until the download is finished. This brings us to the second problem:
2. You need to move the download to a separate thread. When you start the download on a separate thread, then you can continue to draw things to the screen while the download is running in the background. You can use love2d's Channels to send data back and forth between the two threads. For example, you can have one channel on which download URLs are sent to the downloader thread, and another channel that is used by the downloader thread to send back progress updates.
3. Finally, to display a percentage you need to know the file size of the thing you're downloading. That is stored in the response header as "content-length". However, http.request only returns this header after the download is finished. But to display a nice progress bar, you need that information right away. The solution here is to send two requests; the first one only requests the header, and the second request actually downloads the file.
Attached is an example project to show how all of this can be done. It's public domain, so use it however you want
I hope this helps. I'll let you work out how to display the download speed
Let me know if you have any questions.
*Edit: The demo should now run on Windows, too.
*Edit: Updated the demo to fix thread error handling and wrong usage of io.open
*Edit: The previous version was buggy, as Beelz rightfully pointed out. I've rewritten part of the demo to avoid Lua's io module