From 8f30657390dd220ce3dd0f747281edbf585e639d Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sun, 7 Apr 2019 14:36:10 +0200 Subject: [PATCH] Start working on a Downloader app and backing classes in LibGUI. LibGUI is slowly becoming LibKitchensink but I'm okay with this for now. --- Applications/Downloader/.gitignore | 3 ++ Applications/Downloader/Makefile | 32 ++++++++++++++++++++ Applications/Downloader/main.cpp | 30 +++++++++++++++++++ Kernel/makeall.sh | 2 ++ Kernel/sync.sh | 2 ++ LibGUI/GEvent.h | 15 ++++++++++ LibGUI/GEventLoop.cpp | 3 ++ LibGUI/GHttpNetworkJob.cpp | 47 ++++++++++++++++++++++++++++++ LibGUI/GHttpNetworkJob.h | 20 +++++++++++++ LibGUI/GHttpRequest.cpp | 45 ++++++++++++++++++++++++++++ LibGUI/GHttpRequest.h | 34 +++++++++++++++++++++ LibGUI/GHttpResponse.cpp | 11 +++++++ LibGUI/GHttpResponse.h | 23 +++++++++++++++ LibGUI/GIODevice.cpp | 4 +++ LibGUI/GNetworkJob.cpp | 27 +++++++++++++++++ LibGUI/GNetworkJob.h | 36 +++++++++++++++++++++++ LibGUI/GNetworkResponse.cpp | 10 +++++++ LibGUI/GNetworkResponse.h | 18 ++++++++++++ LibGUI/GObject.cpp | 6 +++- LibGUI/GObject.h | 3 ++ LibGUI/Makefile | 5 ++++ 21 files changed, 375 insertions(+), 1 deletion(-) create mode 100644 Applications/Downloader/.gitignore create mode 100644 Applications/Downloader/Makefile create mode 100644 Applications/Downloader/main.cpp create mode 100644 LibGUI/GHttpNetworkJob.cpp create mode 100644 LibGUI/GHttpNetworkJob.h create mode 100644 LibGUI/GHttpRequest.cpp create mode 100644 LibGUI/GHttpRequest.h create mode 100644 LibGUI/GHttpResponse.cpp create mode 100644 LibGUI/GHttpResponse.h create mode 100644 LibGUI/GNetworkJob.cpp create mode 100644 LibGUI/GNetworkJob.h create mode 100644 LibGUI/GNetworkResponse.cpp create mode 100644 LibGUI/GNetworkResponse.h diff --git a/Applications/Downloader/.gitignore b/Applications/Downloader/.gitignore new file mode 100644 index 00000000000..bdc2af77977 --- /dev/null +++ b/Applications/Downloader/.gitignore @@ -0,0 +1,3 @@ +*.o +*.d +Downloader diff --git a/Applications/Downloader/Makefile b/Applications/Downloader/Makefile new file mode 100644 index 00000000000..4138c392f9f --- /dev/null +++ b/Applications/Downloader/Makefile @@ -0,0 +1,32 @@ +OBJS = \ + main.o + +APP = Downloader + +STANDARD_FLAGS = -std=c++17 -Wno-sized-deallocation +WARNING_FLAGS = -Wextra -Wall -Wundef -Wcast-qual -Wwrite-strings -Wimplicit-fallthrough +FLAVOR_FLAGS = -fno-exceptions -fno-rtti +OPTIMIZATION_FLAGS = -Os +INCLUDE_FLAGS = -I../../Servers -I../.. -I. -I../../LibC + +DEFINES = -DSERENITY -DSANITIZE_PTRS -DUSERLAND + +CXXFLAGS = -MMD -MP $(WARNING_FLAGS) $(OPTIMIZATION_FLAGS) $(FLAVOR_FLAGS) $(STANDARD_FLAGS) $(INCLUDE_FLAGS) $(DEFINES) +CXX = i686-pc-serenity-g++ +LD = i686-pc-serenity-g++ +AR = i686-pc-serenity-ar +LDFLAGS = -L../../LibC -L../../LibGUI + +all: $(APP) + +$(APP): $(OBJS) + $(LD) -o $(APP) $(LDFLAGS) $(OBJS) -lgui -lc + +.cpp.o: + @echo "CXX $<"; $(CXX) $(CXXFLAGS) -o $@ -c $< + +-include $(OBJS:%.o=%.d) + +clean: + @echo "CLEAN"; rm -f $(APPS) $(OBJS) *.d + diff --git a/Applications/Downloader/main.cpp b/Applications/Downloader/main.cpp new file mode 100644 index 00000000000..dcca9f086bf --- /dev/null +++ b/Applications/Downloader/main.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + GApplication app(argc, argv); + + GHttpRequest request; + request.set_hostname("www.google.com"); + request.set_path("/"); + + auto job = request.schedule(); + job->on_finish = [&job] (bool success) { + if (!success) { + dbgprintf("on_finish: request failed :(\n"); + return; + } + auto& response = static_cast(*job->response()); + printf("on_receive: code=%d\n", response.code()); + printf("payload:\n"); + printf("%s", response.payload().pointer()); + printf("payload was %d bytes\n", response.payload().size()); + }; + + printf("Entering main loop...\n"); + return app.exec(); +} diff --git a/Kernel/makeall.sh b/Kernel/makeall.sh index 4b7ec3112f7..1d117e43747 100755 --- a/Kernel/makeall.sh +++ b/Kernel/makeall.sh @@ -36,6 +36,8 @@ $make_cmd -C ../Applications/IRCClient clean && \ $make_cmd -C ../Applications/IRCClient && \ $make_cmd -C ../Applications/Taskbar clean && \ $make_cmd -C ../Applications/Taskbar && \ +$make_cmd -C ../Applications/Downloader clean && \ +$make_cmd -C ../Applications/Downloader && \ $make_cmd clean &&\ $make_cmd && \ sudo ./sync.sh diff --git a/Kernel/sync.sh b/Kernel/sync.sh index 0d30033544c..8ba81975f50 100755 --- a/Kernel/sync.sh +++ b/Kernel/sync.sh @@ -94,6 +94,8 @@ cp -v ../Servers/LookupServer/LookupServer mnt/bin/LookupServer cp -v ../Servers/WindowServer/WindowServer mnt/bin/WindowServer cp -v ../Applications/Taskbar/Taskbar mnt/bin/Taskbar ln -s Taskbar mnt/bin/tb +cp -v ../Applications/Downloader/Downloader mnt/bin/Downloader +ln -s Downloader mnt/bin/dl cp -v kernel.map mnt/ sh sync-local.sh umount mnt || ( sleep 0.5 && sync && umount mnt ) diff --git a/LibGUI/GEvent.h b/LibGUI/GEvent.h index 8d5530d9946..67d49bf7206 100644 --- a/LibGUI/GEvent.h +++ b/LibGUI/GEvent.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -28,6 +29,7 @@ public: KeyUp, Timer, DeferredDestroy, + DeferredInvoke, WindowEntered, WindowLeft, WindowBecameInactive, @@ -55,6 +57,19 @@ private: Type m_type { Invalid }; }; +class GDeferredInvocationEvent : public GEvent { + friend class GEventLoop; +public: + GDeferredInvocationEvent(Function invokee) + : GEvent(GEvent::Type::DeferredInvoke) + , m_invokee(move(invokee)) + { + } + +private: + Function m_invokee; +}; + class GWMEvent : public GEvent { public: GWMEvent(Type type, int client_id, int window_id) diff --git a/LibGUI/GEventLoop.cpp b/LibGUI/GEventLoop.cpp index 356ea23e629..b91f4f9f865 100644 --- a/LibGUI/GEventLoop.cpp +++ b/LibGUI/GEventLoop.cpp @@ -158,6 +158,9 @@ int GEventLoop::exec() default: dbgprintf("Event type %u with no receiver :(\n", event.type()); } + } else if (event.type() == GEvent::Type::DeferredInvoke) { + printf("DeferredInvoke: receiver=%s{%p}\n", receiver->class_name(), receiver); + static_cast(event).m_invokee(*receiver); } else { receiver->event(event); } diff --git a/LibGUI/GHttpNetworkJob.cpp b/LibGUI/GHttpNetworkJob.cpp new file mode 100644 index 00000000000..789ad70c5da --- /dev/null +++ b/LibGUI/GHttpNetworkJob.cpp @@ -0,0 +1,47 @@ +#include +#include +#include +#include + +GHttpNetworkJob::GHttpNetworkJob(const GHttpRequest& request) + : m_request(request) +{ +} + +GHttpNetworkJob::~GHttpNetworkJob() +{ +} + +void GHttpNetworkJob::start() +{ + ASSERT(!m_socket); + m_socket = new GTCPSocket(this); + int success = m_socket->connect(m_request.hostname(), m_request.port()); + if (!success) + return did_fail(GNetworkJob::Error::ConnectionFailed); + + auto raw_request = m_request.to_raw_request(); + + printf("raw_request:\n%s\n", raw_request.pointer()); + + success = m_socket->send(raw_request); + if (!success) + return did_fail(GNetworkJob::Error::TransmissionFailed); + + Vector buffer; + while (m_socket->is_connected()) { + auto payload = m_socket->receive(100000); + if (!payload) { + if (m_socket->eof()) + break; + return did_fail(GNetworkJob::Error::TransmissionFailed); + } + buffer.append(payload.pointer(), payload.size()); + } + + auto response = GHttpResponse::create(1, ByteBuffer::copy(buffer.data(), buffer.size())); + deferred_invoke([this, response] (GObject&) { + printf("in the deferred invoke lambda\n"); + did_finish(move(response)); + }); +} diff --git a/LibGUI/GHttpNetworkJob.h b/LibGUI/GHttpNetworkJob.h new file mode 100644 index 00000000000..f7d86f1eaa3 --- /dev/null +++ b/LibGUI/GHttpNetworkJob.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +class GTCPSocket; + +class GHttpNetworkJob final : public GNetworkJob { +public: + explicit GHttpNetworkJob(const GHttpRequest&); + virtual ~GHttpNetworkJob() override; + + virtual void start() override; + + virtual const char* class_name() const override { return "GHttpNetworkJob"; } + +private: + GHttpRequest m_request; + GTCPSocket* m_socket { nullptr }; +}; diff --git a/LibGUI/GHttpRequest.cpp b/LibGUI/GHttpRequest.cpp new file mode 100644 index 00000000000..96e867eb111 --- /dev/null +++ b/LibGUI/GHttpRequest.cpp @@ -0,0 +1,45 @@ +#include +#include +#include +#include + +GHttpRequest::GHttpRequest() +{ +} + +GHttpRequest::~GHttpRequest() +{ +} + +GNetworkJob* GHttpRequest::schedule() +{ + auto* job = new GHttpNetworkJob(*this); + job->start(); + return job; +} + +String GHttpRequest::method_name() const +{ + switch (m_method) { + case Method::GET: + return "GET"; + case Method::HEAD: + return "HEAD"; + case Method::POST: + return "POST"; + default: + ASSERT_NOT_REACHED(); + } +} + +ByteBuffer GHttpRequest::to_raw_request() const +{ + StringBuilder builder; + builder.append(method_name()); + builder.append(' '); + builder.append(m_path); + builder.append(" HTTP/1.0\nHost: "); + builder.append(m_hostname); + builder.append("\n\n"); + return builder.to_byte_buffer(); +} diff --git a/LibGUI/GHttpRequest.h b/LibGUI/GHttpRequest.h new file mode 100644 index 00000000000..68f9673d6f9 --- /dev/null +++ b/LibGUI/GHttpRequest.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +class GNetworkJob; + +class GHttpRequest { +public: + enum Method { Invalid, HEAD, GET, POST }; + + GHttpRequest(); + ~GHttpRequest(); + + String hostname() const { return m_hostname; } + int port() const { return m_port; } + String path() const { return m_path; } + Method method() const { return m_method; } + + void set_hostname(const String& hostname) { m_hostname = hostname; } + void set_port(int port) { m_port = port; } + void set_path(const String& path) { m_path = path; } + void set_method(Method method) { m_method = method; } + + String method_name() const; + ByteBuffer to_raw_request() const; + + GNetworkJob* schedule(); + +private: + String m_hostname; + String m_path; + int m_port { 80 }; + Method m_method { GET }; +}; diff --git a/LibGUI/GHttpResponse.cpp b/LibGUI/GHttpResponse.cpp new file mode 100644 index 00000000000..888432f9aff --- /dev/null +++ b/LibGUI/GHttpResponse.cpp @@ -0,0 +1,11 @@ +#include + +GHttpResponse::GHttpResponse(int code, ByteBuffer&& payload) + : GNetworkResponse(move(payload)) + , m_code(code) +{ +} + +GHttpResponse::~GHttpResponse() +{ +} diff --git a/LibGUI/GHttpResponse.h b/LibGUI/GHttpResponse.h new file mode 100644 index 00000000000..b6bb702b26d --- /dev/null +++ b/LibGUI/GHttpResponse.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +class GHttpResponse : public GNetworkResponse { +public: + virtual ~GHttpResponse() override; + static Retained create(int code, ByteBuffer&& payload) + { + return adopt(*new GHttpResponse(code, move(payload))); + } + + int code() const { return m_code; } + const HashMap& headers() const { return m_headers; } + +private: + GHttpResponse(int code, ByteBuffer&&); + + int m_code { 0 }; + HashMap m_headers; +}; diff --git a/LibGUI/GIODevice.cpp b/LibGUI/GIODevice.cpp index 988278c4110..19a8497c8d4 100644 --- a/LibGUI/GIODevice.cpp +++ b/LibGUI/GIODevice.cpp @@ -42,6 +42,10 @@ ByteBuffer GIODevice::read(int max_size) set_error(errno); return { }; } + if (nread == 0) { + set_eof(true); + return { }; + } buffer.trim(nread); return buffer; } diff --git a/LibGUI/GNetworkJob.cpp b/LibGUI/GNetworkJob.cpp new file mode 100644 index 00000000000..fa002ef214b --- /dev/null +++ b/LibGUI/GNetworkJob.cpp @@ -0,0 +1,27 @@ +#include +#include +#include + +GNetworkJob::GNetworkJob() +{ +} + +GNetworkJob::~GNetworkJob() +{ +} + +void GNetworkJob::did_finish(Retained&& response) +{ + m_response = move(response); + printf("%s{%p} job did_finish!\n", class_name(), this); + ASSERT(on_finish); + on_finish(true); +} + +void GNetworkJob::did_fail(Error error) +{ + m_error = error; + dbgprintf("%s{%} job did_fail! error=%u\n", class_name(), this, (unsigned)error); + ASSERT(on_finish); + on_finish(false); +} diff --git a/LibGUI/GNetworkJob.h b/LibGUI/GNetworkJob.h new file mode 100644 index 00000000000..217e29dc688 --- /dev/null +++ b/LibGUI/GNetworkJob.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +class GNetworkResponse; + +class GNetworkJob : public GObject { +public: + enum class Error { + None, + ConnectionFailed, + TransmissionFailed, + }; + virtual ~GNetworkJob() override; + + Function on_finish; + + bool has_error() const { return m_error != Error::None; } + Error error() const { return m_error; } + GNetworkResponse* response() { return m_response.ptr(); } + const GNetworkResponse* response() const { return m_response.ptr(); } + + virtual void start() = 0; + + virtual const char* class_name() const override { return "GNetworkJob"; } + +protected: + GNetworkJob(); + void did_finish(Retained&&); + void did_fail(Error); + +private: + RetainPtr m_response; + Error m_error { Error::None }; +}; diff --git a/LibGUI/GNetworkResponse.cpp b/LibGUI/GNetworkResponse.cpp new file mode 100644 index 00000000000..64bdbd1b680 --- /dev/null +++ b/LibGUI/GNetworkResponse.cpp @@ -0,0 +1,10 @@ +#include + +GNetworkResponse::GNetworkResponse(ByteBuffer&& payload) + : m_payload(payload) +{ +} + +GNetworkResponse::~GNetworkResponse() +{ +} diff --git a/LibGUI/GNetworkResponse.h b/LibGUI/GNetworkResponse.h new file mode 100644 index 00000000000..6dbeaf5c643 --- /dev/null +++ b/LibGUI/GNetworkResponse.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +class GNetworkResponse : public Retainable { +public: + virtual ~GNetworkResponse(); + + bool is_error() const { return m_error; } + const ByteBuffer& payload() const { return m_payload; } + +protected: + explicit GNetworkResponse(ByteBuffer&&); + + bool m_error { false }; + ByteBuffer m_payload; +}; diff --git a/LibGUI/GObject.cpp b/LibGUI/GObject.cpp index 4dbbe4bc0b6..3298fa42180 100644 --- a/LibGUI/GObject.cpp +++ b/LibGUI/GObject.cpp @@ -99,5 +99,9 @@ void GObject::dump_tree(int indent) for (auto* child : children()) { child->dump_tree(indent + 2); } - +} + +void GObject::deferred_invoke(Function invokee) +{ + GEventLoop::current().post_event(*this, make(move(invokee))); } diff --git a/LibGUI/GObject.h b/LibGUI/GObject.h index 7c0cb4f31ec..4a619900849 100644 --- a/LibGUI/GObject.h +++ b/LibGUI/GObject.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -32,6 +33,8 @@ public: void dump_tree(int indent = 0); + void deferred_invoke(Function); + virtual bool is_widget() const { return false; } virtual bool is_window() const { return false; } diff --git a/LibGUI/Makefile b/LibGUI/Makefile index 9660229968b..627b06965af 100644 --- a/LibGUI/Makefile +++ b/LibGUI/Makefile @@ -58,6 +58,11 @@ LIBGUI_OBJS = \ GFileSystemModel.o \ GSplitter.o \ GTimer.o \ + GNetworkJob.o \ + GNetworkResponse.o \ + GHttpRequest.o \ + GHttpResponse.o \ + GHttpNetworkJob.o \ GWindow.o OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS)