Start working on a Downloader app and backing classes in LibGUI.

LibGUI is slowly becoming LibKitchensink but I'm okay with this for now.
This commit is contained in:
Andreas Kling 2019-04-07 14:36:10 +02:00
parent c7365a00f8
commit 8f30657390
Notes: sideshowbarker 2024-07-19 14:47:54 +09:00
21 changed files with 375 additions and 1 deletions

3
Applications/Downloader/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
*.o
*.d
Downloader

View file

@ -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

View file

@ -0,0 +1,30 @@
#include <LibGUI/GApplication.h>
#include <LibGUI/GHttpRequest.h>
#include <LibGUI/GHttpResponse.h>
#include <LibGUI/GNetworkJob.h>
#include <stdio.h>
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<const GHttpResponse&>(*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();
}

View file

@ -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

View file

@ -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 )

View file

@ -5,6 +5,7 @@
#include <AK/AKString.h>
#include <AK/Types.h>
#include <AK/WeakPtr.h>
#include <AK/Function.h>
#include <Kernel/KeyCode.h>
#include <LibGUI/GWindowType.h>
@ -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<void(GObject&)> invokee)
: GEvent(GEvent::Type::DeferredInvoke)
, m_invokee(move(invokee))
{
}
private:
Function<void(GObject&)> m_invokee;
};
class GWMEvent : public GEvent {
public:
GWMEvent(Type type, int client_id, int window_id)

View file

@ -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<GDeferredInvocationEvent&>(event).m_invokee(*receiver);
} else {
receiver->event(event);
}

View file

@ -0,0 +1,47 @@
#include <LibGUI/GHttpNetworkJob.h>
#include <LibGUI/GHttpResponse.h>
#include <LibGUI/GTCPSocket.h>
#include <stdio.h>
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<byte> 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));
});
}

20
LibGUI/GHttpNetworkJob.h Normal file
View file

@ -0,0 +1,20 @@
#pragma once
#include <LibGUI/GNetworkJob.h>
#include <LibGUI/GHttpRequest.h>
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 };
};

45
LibGUI/GHttpRequest.cpp Normal file
View file

@ -0,0 +1,45 @@
#include <LibGUI/GHttpRequest.h>
#include <LibGUI/GHttpNetworkJob.h>
#include <LibGUI/GEventLoop.h>
#include <AK/StringBuilder.h>
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();
}

34
LibGUI/GHttpRequest.h Normal file
View file

@ -0,0 +1,34 @@
#pragma once
#include <AK/AKString.h>
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 };
};

11
LibGUI/GHttpResponse.cpp Normal file
View file

@ -0,0 +1,11 @@
#include <LibGUI/GHttpResponse.h>
GHttpResponse::GHttpResponse(int code, ByteBuffer&& payload)
: GNetworkResponse(move(payload))
, m_code(code)
{
}
GHttpResponse::~GHttpResponse()
{
}

23
LibGUI/GHttpResponse.h Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include <LibGUI/GNetworkResponse.h>
#include <AK/AKString.h>
#include <AK/HashMap.h>
class GHttpResponse : public GNetworkResponse {
public:
virtual ~GHttpResponse() override;
static Retained<GHttpResponse> create(int code, ByteBuffer&& payload)
{
return adopt(*new GHttpResponse(code, move(payload)));
}
int code() const { return m_code; }
const HashMap<String, String>& headers() const { return m_headers; }
private:
GHttpResponse(int code, ByteBuffer&&);
int m_code { 0 };
HashMap<String, String> m_headers;
};

View file

@ -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;
}

27
LibGUI/GNetworkJob.cpp Normal file
View file

@ -0,0 +1,27 @@
#include <LibGUI/GNetworkJob.h>
#include <LibGUI/GNetworkResponse.h>
#include <stdio.h>
GNetworkJob::GNetworkJob()
{
}
GNetworkJob::~GNetworkJob()
{
}
void GNetworkJob::did_finish(Retained<GNetworkResponse>&& 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);
}

36
LibGUI/GNetworkJob.h Normal file
View file

@ -0,0 +1,36 @@
#pragma once
#include <LibGUI/GObject.h>
#include <AK/Function.h>
class GNetworkResponse;
class GNetworkJob : public GObject {
public:
enum class Error {
None,
ConnectionFailed,
TransmissionFailed,
};
virtual ~GNetworkJob() override;
Function<void(bool success)> 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<GNetworkResponse>&&);
void did_fail(Error);
private:
RetainPtr<GNetworkResponse> m_response;
Error m_error { Error::None };
};

View file

@ -0,0 +1,10 @@
#include <LibGUI/GNetworkResponse.h>
GNetworkResponse::GNetworkResponse(ByteBuffer&& payload)
: m_payload(payload)
{
}
GNetworkResponse::~GNetworkResponse()
{
}

18
LibGUI/GNetworkResponse.h Normal file
View file

@ -0,0 +1,18 @@
#pragma once
#include <AK/Retainable.h>
#include <AK/ByteBuffer.h>
class GNetworkResponse : public Retainable<GNetworkResponse> {
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;
};

View file

@ -99,5 +99,9 @@ void GObject::dump_tree(int indent)
for (auto* child : children()) {
child->dump_tree(indent + 2);
}
}
void GObject::deferred_invoke(Function<void(GObject&)> invokee)
{
GEventLoop::current().post_event(*this, make<GDeferredInvocationEvent>(move(invokee)));
}

View file

@ -1,5 +1,6 @@
#pragma once
#include <AK/Function.h>
#include <AK/Vector.h>
#include <AK/Weakable.h>
@ -32,6 +33,8 @@ public:
void dump_tree(int indent = 0);
void deferred_invoke(Function<void(GObject&)>);
virtual bool is_widget() const { return false; }
virtual bool is_window() const { return false; }

View file

@ -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)