1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 19:38:12 +00:00

ProtocolServer: Stream the downloaded data if possible

This patchset makes ProtocolServer stream the downloads to its client
(LibProtocol), and as such changes the download API; a possible
download lifecycle could be as such:
notation = client->server:'>', server->client:'<', pipe activity:'*'
```
> StartDownload(GET, url, headers, {})
< Response(0, fd 8)
* {data, 1024b}
< HeadersBecameAvailable(0, response_headers, 200)
< DownloadProgress(0, 4K, 1024)
* {data, 1024b}
* {data, 1024b}
< DownloadProgress(0, 4K, 2048)
* {data, 1024b}
< DownloadProgress(0, 4K, 1024)
< DownloadFinished(0, true, 4K)
```

Since managing the received file descriptor is a pain, LibProtocol
implements `Download::stream_into(OutputStream)`, which can be used to
stream the download into any given output stream (be it a file, or
memory, or writing stuff with a delay, etc.).
Also, as some of the users of this API require all the downloaded data
upfront, LibProtocol also implements `set_should_buffer_all_input()`,
which causes the download instance to buffer all the data until the
download is complete, and to call the `on_buffered_download_finish`
hook.
This commit is contained in:
AnotherTest 2020-12-26 17:14:12 +03:30 committed by Andreas Kling
parent 36d642ee75
commit 4a2da10e38
55 changed files with 528 additions and 235 deletions

View file

@ -24,6 +24,7 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <AK/FileStream.h>
#include <AK/GenericLexer.h>
#include <AK/LexicalPath.h>
#include <AK/NumberFormat.h>
@ -116,29 +117,50 @@ private:
bool m_might_be_wrong { false };
};
static void do_write(ReadonlyBytes payload)
{
size_t length_remaining = payload.size();
size_t length_written = 0;
while (length_remaining > 0) {
auto nwritten = fwrite(payload.data() + length_written, sizeof(char), length_remaining, stdout);
if (nwritten > 0) {
length_remaining -= nwritten;
length_written += nwritten;
continue;
}
template<typename ConditionT>
class ConditionalOutputFileStream final : public OutputFileStream {
public:
template<typename... Args>
ConditionalOutputFileStream(ConditionT&& condition, Args... args)
: OutputFileStream(args...)
, m_condition(condition)
{
}
if (feof(stdout)) {
fprintf(stderr, "pro: unexpected eof while writing\n");
~ConditionalOutputFileStream()
{
if (!m_condition())
return;
}
if (ferror(stdout)) {
fprintf(stderr, "pro: error while writing\n");
return;
if (!m_buffer.is_empty()) {
OutputFileStream::write(m_buffer);
m_buffer.clear();
}
}
}
private:
size_t write(ReadonlyBytes bytes) override
{
if (!m_condition()) {
write_to_buffer:;
m_buffer.append(bytes.data(), bytes.size());
return bytes.size();
}
if (!m_buffer.is_empty()) {
auto size = OutputFileStream::write(m_buffer);
m_buffer = m_buffer.slice(size, m_buffer.size() - size);
}
if (!m_buffer.is_empty())
goto write_to_buffer;
return OutputFileStream::write(bytes);
}
ConditionT m_condition;
ByteBuffer m_buffer;
};
int main(int argc, char** argv)
{
@ -195,6 +217,8 @@ int main(int argc, char** argv)
timeval prev_time, current_time, time_diff;
gettimeofday(&prev_time, nullptr);
bool received_actual_headers = false;
download->on_progress = [&](Optional<u32> maybe_total_size, u32 downloaded_size) {
fprintf(stderr, "\r\033[2K");
if (maybe_total_size.has_value()) {
@ -215,10 +239,13 @@ int main(int argc, char** argv)
previous_downloaded_size = downloaded_size;
prev_time = current_time;
};
download->on_finish = [&](bool success, auto payload, auto, auto& response_headers, auto) {
fprintf(stderr, "\033]9;-1;\033\\");
fprintf(stderr, "\n");
if (success && save_at_provided_name) {
if (save_at_provided_name) {
download->on_headers_received = [&](auto& response_headers, auto status_code) {
if (received_actual_headers)
return;
dbg() << "Received headers! response code = " << status_code.value_or(0);
received_actual_headers = true; // And not trailers!
String output_name;
if (auto content_disposition = response_headers.get("Content-Disposition"); content_disposition.has_value()) {
auto& value = content_disposition.value();
@ -245,17 +272,26 @@ int main(int argc, char** argv)
if (freopen(output_name.characters(), "w", stdout) == nullptr) {
perror("freopen");
success = false; // oops!
loop.quit(1);
return;
}
}
if (success)
do_write(payload);
else
};
}
download->on_finish = [&](bool success, auto) {
fprintf(stderr, "\033]9;-1;\033\\");
fprintf(stderr, "\n");
if (!success)
fprintf(stderr, "Download failed :(\n");
loop.quit(0);
};
auto output_stream = ConditionalOutputFileStream { [&] { return save_at_provided_name ? received_actual_headers : true; }, stdout };
download->stream_into(output_stream);
dbgprintf("started download with id %d\n", download->id());
return loop.exec();
auto rc = loop.exec();
// FIXME: This shouldn't be needed.
fclose(stdout);
return rc;
}