diff options
author | AnotherTest <ali.mpfard@gmail.com> | 2020-12-26 17:14:12 +0330 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-12-30 13:31:55 +0100 |
commit | 4a2da10e38c2b413c9e4db411e47d1b90d98d8ee (patch) | |
tree | 893aef9f1f760622e739183050a9172b717025f0 /Libraries/LibProtocol/Download.cpp | |
parent | 36d642ee7585801e1abe8a421b30f1a779be3bab (diff) | |
download | serenity-4a2da10e38c2b413c9e4db411e47d1b90d98d8ee.zip |
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.
Diffstat (limited to 'Libraries/LibProtocol/Download.cpp')
-rw-r--r-- | Libraries/LibProtocol/Download.cpp | 88 |
1 files changed, 75 insertions, 13 deletions
diff --git a/Libraries/LibProtocol/Download.cpp b/Libraries/LibProtocol/Download.cpp index b71127951b..f5011d04a5 100644 --- a/Libraries/LibProtocol/Download.cpp +++ b/Libraries/LibProtocol/Download.cpp @@ -41,25 +41,81 @@ bool Download::stop() return m_client->stop_download({}, *this); } -void Download::did_finish(Badge<Client>, bool success, Optional<u32> status_code, u32 total_size, i32 shbuf_id, const IPC::Dictionary& response_headers) +void Download::stream_into(OutputStream& stream) { - if (!on_finish) + ASSERT(!m_internal_stream_data); + + auto notifier = Core::Notifier::construct(fd(), Core::Notifier::Read); + + m_internal_stream_data = make<InternalStreamData>(fd()); + m_internal_stream_data->read_notifier = notifier; + + auto user_on_finish = move(on_finish); + on_finish = [this](auto success, auto total_size) { + m_internal_stream_data->success = success; + m_internal_stream_data->total_size = total_size; + m_internal_stream_data->download_done = true; + }; + + notifier->on_ready_to_read = [this, &stream, user_on_finish = move(user_on_finish)] { + constexpr size_t buffer_size = 1 * KiB; + static char buf[buffer_size]; + auto nread = m_internal_stream_data->read_stream.read({ buf, buffer_size }); + if (!stream.write_or_error({ buf, nread })) { + // FIXME: What do we do here? + TODO(); + } + + if (m_internal_stream_data->read_stream.eof() || (m_internal_stream_data->download_done && !m_internal_stream_data->success)) { + m_internal_stream_data->read_notifier->close(); + user_on_finish(m_internal_stream_data->success, m_internal_stream_data->total_size); + } else { + m_internal_stream_data->read_stream.handle_any_error(); + } + }; +} + +void Download::set_should_buffer_all_input(bool value) +{ + if (m_should_buffer_all_input == value) return; - ReadonlyBytes payload; - RefPtr<SharedBuffer> shared_buffer; - if (success && shbuf_id != -1) { - shared_buffer = SharedBuffer::create_from_shbuf_id(shbuf_id); - payload = { shared_buffer->data<void>(), total_size }; + if (m_internal_buffered_data && !value) { + m_internal_buffered_data = nullptr; + m_should_buffer_all_input = false; + return; } - // FIXME: It's a bit silly that we copy the response headers here just so we can move them into a HashMap with different traits. - HashMap<String, String, CaseInsensitiveStringTraits> caseless_response_headers; - response_headers.for_each_entry([&](auto& name, auto& value) { - caseless_response_headers.set(name, value); - }); + ASSERT(!m_internal_stream_data); + ASSERT(!m_internal_buffered_data); + ASSERT(on_buffered_download_finish); // Not having this set makes no sense. + m_internal_buffered_data = make<InternalBufferedData>(fd()); + m_should_buffer_all_input = true; - on_finish(success, payload, move(shared_buffer), caseless_response_headers, status_code); + on_headers_received = [this](auto& headers, auto response_code) { + m_internal_buffered_data->response_headers = headers; + m_internal_buffered_data->response_code = move(response_code); + }; + + on_finish = [this](auto success, u32 total_size) { + auto output_buffer = m_internal_buffered_data->payload_stream.copy_into_contiguous_buffer(); + on_buffered_download_finish( + success, + total_size, + m_internal_buffered_data->response_headers, + m_internal_buffered_data->response_code, + output_buffer); + }; + + stream_into(m_internal_buffered_data->payload_stream); +} + +void Download::did_finish(Badge<Client>, bool success, u32 total_size) +{ + if (!on_finish) + return; + + on_finish(success, total_size); } void Download::did_progress(Badge<Client>, Optional<u32> total_size, u32 downloaded_size) @@ -68,6 +124,12 @@ void Download::did_progress(Badge<Client>, Optional<u32> total_size, u32 downloa on_progress(total_size, downloaded_size); } +void Download::did_receive_headers(Badge<Client>, const HashMap<String, String, CaseInsensitiveStringTraits>& response_headers, Optional<u32> response_code) +{ + if (on_headers_received) + on_headers_received(response_headers, response_code); +} + void Download::did_request_certificates(Badge<Client>) { if (on_certificate_requested) { |