summaryrefslogtreecommitdiff
path: root/LibGUI
diff options
context:
space:
mode:
authorAndreas Kling <awesomekling@gmail.com>2019-03-18 14:09:58 +0100
committerAndreas Kling <awesomekling@gmail.com>2019-03-18 14:09:58 +0100
commit8e3d0a23d555b2464aba5a07a90092535cec19e1 (patch)
tree551456726750a407e82f76d9b626fa1d78775e70 /LibGUI
parentd466f2634d94df44402c5779395a8f3798151d45 (diff)
downloadserenity-8e3d0a23d555b2464aba5a07a90092535cec19e1.zip
LibGUI: Add GTCPSocket and base class GSocket (inherits from GIODevice.)
And use these to do the line-by-line reading automagically instead of having that logic in IRCClient. This will definitely come in handy.
Diffstat (limited to 'LibGUI')
-rw-r--r--LibGUI/GFile.cpp14
-rw-r--r--LibGUI/GFile.h1
-rw-r--r--LibGUI/GIODevice.cpp68
-rw-r--r--LibGUI/GIODevice.h7
-rw-r--r--LibGUI/GSocket.cpp64
-rw-r--r--LibGUI/GSocket.h68
-rw-r--r--LibGUI/GTCPSocket.cpp19
-rw-r--r--LibGUI/GTCPSocket.h10
-rw-r--r--LibGUI/Makefile2
9 files changed, 230 insertions, 23 deletions
diff --git a/LibGUI/GFile.cpp b/LibGUI/GFile.cpp
index 45b083191c..e0ff42e0bb 100644
--- a/LibGUI/GFile.cpp
+++ b/LibGUI/GFile.cpp
@@ -40,17 +40,3 @@ bool GFile::open(GIODevice::OpenMode mode)
set_mode(mode);
return true;
}
-
-bool GFile::close()
-{
- if (fd() < 0 || mode() == NotOpen)
- return false;
- int rc = ::close(fd());
- if (rc < 0) {
- set_error(rc);
- return false;
- }
- set_fd(-1);
- set_mode(GIODevice::NotOpen);
- return true;
-}
diff --git a/LibGUI/GFile.h b/LibGUI/GFile.h
index de7325fb93..89aadf0aad 100644
--- a/LibGUI/GFile.h
+++ b/LibGUI/GFile.h
@@ -13,7 +13,6 @@ public:
void set_filename(const String& filename) { m_filename = filename; }
virtual bool open(GIODevice::OpenMode) override;
- virtual bool close() override;
virtual const char* class_name() const override { return "GFile"; }
diff --git a/LibGUI/GIODevice.cpp b/LibGUI/GIODevice.cpp
index d62d36622f..0b0f8de1d1 100644
--- a/LibGUI/GIODevice.cpp
+++ b/LibGUI/GIODevice.cpp
@@ -1,5 +1,7 @@
#include <LibGUI/GIODevice.h>
#include <unistd.h>
+#include <sys/select.h>
+#include <stdio.h>
GIODevice::GIODevice(GObject* parent)
: GObject(parent)
@@ -44,26 +46,63 @@ ByteBuffer GIODevice::read(int max_size)
return buffer;
}
+bool GIODevice::can_read() const
+{
+ // FIXME: Can we somehow remove this once GSocket is implemented using non-blocking sockets?
+ fd_set rfds;
+ FD_ZERO(&rfds);
+ FD_SET(m_fd, &rfds);
+ struct timeval timeout { 0, 0 };
+ int rc = select(m_fd + 1, &rfds, nullptr, nullptr, &timeout);
+ if (rc < 0) {
+ // NOTE: We don't set m_error here.
+ perror("GIODevice::can_read: select");
+ return false;
+ }
+ return FD_ISSET(m_fd, &rfds);
+}
+
+bool GIODevice::can_read_line()
+{
+ if (m_eof && !m_buffered_data.is_empty())
+ return true;
+ if (m_buffered_data.contains_slow('\n'))
+ return true;
+ if (!can_read())
+ return false;
+ populate_read_buffer();
+ return m_buffered_data.contains_slow('\n');
+}
+
ByteBuffer GIODevice::read_line(int max_size)
{
if (m_fd < 0)
return { };
if (!max_size)
return { };
- auto line = ByteBuffer::create_uninitialized(max_size);
- int line_index = 0;
- while (line_index < line.size()) {
- if (line_index >= m_buffered_data.size()) {
- if (!populate_read_buffer())
- return { };
+ if (!can_read_line())
+ return { };
+ if (m_eof) {
+ if (m_buffered_data.size() > max_size) {
+ dbgprintf("GIODevice::read_line: At EOF but there's more than max_size(%d) buffered\n", max_size);
+ return { };
}
+ auto buffer = ByteBuffer::copy(m_buffered_data.data(), m_buffered_data.size());
+ m_buffered_data.clear();
+ return buffer;
+ }
+ auto line = ByteBuffer::create_uninitialized(max_size + 1);
+ int line_index = 0;
+ while (line_index < max_size) {
byte ch = m_buffered_data[line_index];
line[line_index++] = ch;
if (ch == '\n') {
Vector<byte> new_buffered_data;
new_buffered_data.append(m_buffered_data.data() + line_index, m_buffered_data.size() - line_index);
m_buffered_data = move(new_buffered_data);
- line.trim(line_index);
+ line[line_index] = '\0';
+ line.trim(line_index + 1);
+ dbgprintf("GIODevice::read_line: '%s'\n", line.pointer());
return line;
}
}
@@ -84,6 +123,21 @@ bool GIODevice::populate_read_buffer()
set_eof(true);
return false;
}
+ buffer.trim(nread);
m_buffered_data.append(buffer.pointer(), buffer.size());
return true;
}
+
+bool GIODevice::close()
+{
+ if (fd() < 0 || mode() == NotOpen)
+ return false;
+ int rc = ::close(fd());
+ if (rc < 0) {
+ set_error(rc);
+ return false;
+ }
+ set_fd(-1);
+ set_mode(GIODevice::NotOpen);
+ return true;
+}
diff --git a/LibGUI/GIODevice.h b/LibGUI/GIODevice.h
index 744e1997d0..d7d7cd094c 100644
--- a/LibGUI/GIODevice.h
+++ b/LibGUI/GIODevice.h
@@ -29,8 +29,13 @@ public:
ByteBuffer read(int max_size);
ByteBuffer read_line(int max_size);
+ // FIXME: I would like this to be const but currently it needs to call populate_read_buffer().
+ bool can_read_line();
+
+ bool can_read() const;
+
virtual bool open(GIODevice::OpenMode) = 0;
- virtual bool close() = 0;
+ virtual bool close();
virtual const char* class_name() const override { return "GIODevice"; }
diff --git a/LibGUI/GSocket.cpp b/LibGUI/GSocket.cpp
new file mode 100644
index 0000000000..78343f2225
--- /dev/null
+++ b/LibGUI/GSocket.cpp
@@ -0,0 +1,64 @@
+#include <LibGUI/GSocket.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+
+GSocket::GSocket(Type type, GObject* parent)
+ : GIODevice(parent)
+ , m_type(type)
+{
+}
+
+GSocket::~GSocket()
+{
+}
+
+bool GSocket::connect(const GSocketAddress& address, int port)
+{
+ ASSERT(!is_connected());
+ ASSERT(address.type() == GSocketAddress::Type::IPv4);
+ ASSERT(port > 0 && port <= 65535);
+
+ struct sockaddr_in addr;
+ memset(&addr, 0, sizeof(addr));
+ auto ipv4_address = address.ipv4_address();
+ memcpy(&addr.sin_addr.s_addr, &ipv4_address, sizeof(IPv4Address));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+
+ printf("Connecting to %s...", address.to_string().characters());
+ fflush(stdout);
+ int rc = ::connect(fd(), (struct sockaddr*)&addr, sizeof(addr));
+ if (rc < 0) {
+ perror("connect");
+ exit(1);
+ }
+ printf("ok!\n");
+
+ m_destination_address = address;
+ m_destination_port = port;
+ m_connected = true;
+ return true;
+}
+
+ByteBuffer GSocket::receive(int max_size)
+{
+ auto buffer = read(max_size);
+ if (eof()) {
+ dbgprintf("GSocket{%p}: Connection appears to have closed in receive().\n", this);
+ m_connected = false;
+ }
+ return buffer;
+}
+
+bool GSocket::send(const ByteBuffer& data)
+{
+ int nsent = ::send(fd(), data.pointer(), data.size(), 0);
+ if (nsent < 0) {
+ set_error(nsent);
+ return false;
+ }
+ ASSERT(nsent == data.size());
+ return true;
+}
diff --git a/LibGUI/GSocket.h b/LibGUI/GSocket.h
new file mode 100644
index 0000000000..a4871f6480
--- /dev/null
+++ b/LibGUI/GSocket.h
@@ -0,0 +1,68 @@
+#pragma once
+
+#include <LibGUI/GIODevice.h>
+#include <AK/AKString.h>
+#include <Kernel/IPv4.h>
+
+class GSocketAddress {
+public:
+ enum class Type { Invalid, IPv4, Local };
+
+ GSocketAddress() { }
+ GSocketAddress(const IPv4Address& address)
+ : m_type(Type::IPv4)
+ , m_ipv4_address(address)
+ {
+ }
+
+ Type type() const { return m_type; }
+ bool is_valid() const { return m_type != Type::Invalid; }
+ IPv4Address ipv4_address() const { return m_ipv4_address; }
+
+ String to_string() const
+ {
+ switch (m_type) {
+ case Type::IPv4: return m_ipv4_address.to_string();
+ default: return "[GSocketAddress]";
+ }
+ }
+
+private:
+ Type m_type { Type::Invalid };
+ IPv4Address m_ipv4_address;
+};
+
+class GSocket : public GIODevice {
+public:
+ enum class Type { Invalid, TCP, UDP };
+ virtual ~GSocket() override;
+
+ bool connect(const GSocketAddress&, int port);
+
+ ByteBuffer receive(int max_size);
+ bool send(const ByteBuffer&);
+
+ bool is_connected() const { return m_connected; }
+
+ GSocketAddress source_address() const { return m_source_address; }
+ int source_port() const { return m_source_port; }
+
+ GSocketAddress destination_address() const { return m_source_address; }
+ int destination_port() const { return m_destination_port; }
+
+ virtual const char* class_name() const override { return "GSocket"; }
+
+protected:
+ GSocket(Type, GObject* parent);
+
+ GSocketAddress m_source_address;
+ GSocketAddress m_destination_address;
+ int m_source_port { -1 };
+ int m_destination_port { -1 };
+ bool m_connected { false };
+
+private:
+ virtual bool open(GIODevice::OpenMode) override { ASSERT_NOT_REACHED(); }
+ Type m_type { Type::Invalid };
+
+};
diff --git a/LibGUI/GTCPSocket.cpp b/LibGUI/GTCPSocket.cpp
new file mode 100644
index 0000000000..1194e8adf0
--- /dev/null
+++ b/LibGUI/GTCPSocket.cpp
@@ -0,0 +1,19 @@
+#include <LibGUI/GTCPSocket.h>
+#include <sys/socket.h>
+
+GTCPSocket::GTCPSocket(GObject* parent)
+ : GSocket(GSocket::Type::TCP, parent)
+{
+ int fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (fd < 0) {
+ set_error(fd);
+ } else {
+ set_fd(fd);
+ set_mode(GIODevice::ReadWrite);
+ set_error(0);
+ }
+}
+
+GTCPSocket::~GTCPSocket()
+{
+}
diff --git a/LibGUI/GTCPSocket.h b/LibGUI/GTCPSocket.h
new file mode 100644
index 0000000000..691da1731e
--- /dev/null
+++ b/LibGUI/GTCPSocket.h
@@ -0,0 +1,10 @@
+#include <LibGUI/GSocket.h>
+
+class GTCPSocket final : public GSocket {
+public:
+ explicit GTCPSocket(GObject* parent);
+ virtual ~GTCPSocket() override;
+
+private:
+};
+
diff --git a/LibGUI/Makefile b/LibGUI/Makefile
index c39478d130..098c3cf779 100644
--- a/LibGUI/Makefile
+++ b/LibGUI/Makefile
@@ -40,6 +40,8 @@ LIBGUI_OBJS = \
GStackWidget.o \
GEvent.o \
GScrollableWidget.o \
+ GSocket.o \
+ GTCPSocket.o \
GWindow.o
OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS)