summaryrefslogtreecommitdiff
path: root/Libraries/LibCore
diff options
context:
space:
mode:
authorAndreas Kling <awesomekling@gmail.com>2019-07-04 16:16:50 +0200
committerAndreas Kling <awesomekling@gmail.com>2019-07-04 16:16:50 +0200
commit04b9dc2d30cfc9b383029f6a4b02e2725108b0ae (patch)
treee117a998173b767f9fd009d49c4f8573d8b85432 /Libraries/LibCore
parent63814ffebf16291419745cd8ba29a4d2fd888563 (diff)
downloadserenity-04b9dc2d30cfc9b383029f6a4b02e2725108b0ae.zip
Libraries: Create top level directory for libraries.
Things were getting a little crowded in the project root, so this patch moves the Lib*/ directories into Libraries/.
Diffstat (limited to 'Libraries/LibCore')
-rw-r--r--Libraries/LibCore/CArgsParser.cpp237
-rw-r--r--Libraries/LibCore/CArgsParser.h69
-rw-r--r--Libraries/LibCore/CConfigFile.cpp233
-rw-r--r--Libraries/LibCore/CConfigFile.h51
-rw-r--r--Libraries/LibCore/CDirIterator.cpp67
-rw-r--r--Libraries/LibCore/CDirIterator.h29
-rw-r--r--Libraries/LibCore/CElapsedTimer.cpp20
-rw-r--r--Libraries/LibCore/CElapsedTimer.h18
-rw-r--r--Libraries/LibCore/CEvent.cpp12
-rw-r--r--Libraries/LibCore/CEvent.h74
-rw-r--r--Libraries/LibCore/CEventLoop.cpp302
-rw-r--r--Libraries/LibCore/CEventLoop.h89
-rw-r--r--Libraries/LibCore/CFile.cpp51
-rw-r--r--Libraries/LibCore/CFile.h28
-rw-r--r--Libraries/LibCore/CHttpJob.cpp118
-rw-r--r--Libraries/LibCore/CHttpJob.h33
-rw-r--r--Libraries/LibCore/CHttpRequest.cpp44
-rw-r--r--Libraries/LibCore/CHttpRequest.h39
-rw-r--r--Libraries/LibCore/CHttpResponse.cpp12
-rw-r--r--Libraries/LibCore/CHttpResponse.h23
-rw-r--r--Libraries/LibCore/CIODevice.cpp242
-rw-r--r--Libraries/LibCore/CIODevice.h74
-rw-r--r--Libraries/LibCore/CLock.h125
-rw-r--r--Libraries/LibCore/CNetworkJob.cpp43
-rw-r--r--Libraries/LibCore/CNetworkJob.h39
-rw-r--r--Libraries/LibCore/CNetworkResponse.cpp10
-rw-r--r--Libraries/LibCore/CNetworkResponse.h18
-rw-r--r--Libraries/LibCore/CNotifier.cpp15
-rw-r--r--Libraries/LibCore/CNotifier.h26
-rw-r--r--Libraries/LibCore/CObject.cpp113
-rw-r--r--Libraries/LibCore/CObject.h94
-rw-r--r--Libraries/LibCore/CProcessStatisticsReader.cpp58
-rw-r--r--Libraries/LibCore/CProcessStatisticsReader.h29
-rw-r--r--Libraries/LibCore/CSocket.cpp93
-rw-r--r--Libraries/LibCore/CSocket.h48
-rw-r--r--Libraries/LibCore/CSocketAddress.h37
-rw-r--r--Libraries/LibCore/CTCPSocket.cpp19
-rw-r--r--Libraries/LibCore/CTCPSocket.h9
-rw-r--r--Libraries/LibCore/CTimer.cpp61
-rw-r--r--Libraries/LibCore/CTimer.h41
-rw-r--r--Libraries/LibCore/CUserInfo.cpp17
-rw-r--r--Libraries/LibCore/CUserInfo.h1
-rw-r--r--Libraries/LibCore/Makefile49
-rwxr-xr-xLibraries/LibCore/install.sh5
44 files changed, 2815 insertions, 0 deletions
diff --git a/Libraries/LibCore/CArgsParser.cpp b/Libraries/LibCore/CArgsParser.cpp
new file mode 100644
index 0000000000..12ce7c90b4
--- /dev/null
+++ b/Libraries/LibCore/CArgsParser.cpp
@@ -0,0 +1,237 @@
+#include "CArgsParser.h"
+#include <AK/StringBuilder.h>
+
+#include <stdio.h>
+
+bool CArgsParserResult::is_present(const String& arg_name) const
+{
+ return m_args.contains(arg_name);
+}
+
+String CArgsParserResult::get(const String& arg_name) const
+{
+ return m_args.get(arg_name);
+}
+
+const Vector<String>& CArgsParserResult::get_single_values() const
+{
+ return m_single_values;
+}
+
+CArgsParser::Arg::Arg(const String& name, const String& description, bool required)
+ : name(name)
+ , description(description)
+ , required(required)
+{
+}
+
+CArgsParser::Arg::Arg(const String& name, const String& value_name, const String& description, bool required)
+ : name(name)
+ , description(description)
+ , value_name(value_name)
+ , required(required)
+{
+}
+
+CArgsParser::CArgsParser(const String& program_name)
+ : m_program_name(program_name)
+ , m_prefix("-")
+{
+}
+
+CArgsParserResult CArgsParser::parse(int argc, char** argv)
+{
+ CArgsParserResult res;
+
+ // We should have at least one parameter
+ if (argc < 2)
+ return {};
+
+ // We parse the first parameter at the index 1
+ if (parse_next_param(1, argv, argc - 1, res) != 0)
+ return {};
+
+ if (!check_required_args(res))
+ return {};
+
+ return res;
+}
+
+int CArgsParser::parse_next_param(int index, char** argv, const int params_left, CArgsParserResult& res)
+{
+ if (params_left == 0)
+ return 0;
+
+ String param = argv[index];
+
+ // We check if the prefix is found at the beginning of the param name
+ if (is_param_valid(param)) {
+ auto prefix_length = m_prefix.length();
+ String param_name = param.substring(prefix_length, param.length() - prefix_length);
+
+ auto arg = m_args.find(param_name);
+ if (arg == m_args.end()) {
+ printf("Unknown arg \"");
+ if (!param_name.is_null())
+ printf("%s", param_name.characters());
+ printf("\"\n");
+ return -1;
+ }
+
+ // If this parameter must be followed by a value, we look for it
+ if (!arg->value.value_name.is_null()) {
+ if (params_left < 1) {
+ printf("Missing value for argument %s\n", arg->value.name.characters());
+ return -1;
+ }
+
+ String next = String(argv[index + 1]);
+
+ if (is_param_valid(next)) {
+ printf("Missing value for argument %s\n", arg->value.name.characters());
+ return -1;
+ }
+
+ res.m_args.set(arg->value.name, next);
+ return parse_next_param(index + 2, argv, params_left - 2, res);
+ }
+
+ // Single argument, not followed by a value
+ res.m_args.set(arg->value.name, "");
+ return parse_next_param(index + 1, argv, params_left - 1, res);
+ }
+
+ // Else, it's a value alone, a file name parameter for example
+ res.m_single_values.append(param);
+ return parse_next_param(index + 1, argv, params_left - 1, res);
+}
+
+bool CArgsParser::is_param_valid(const String& param_name)
+{
+ return param_name.substring(0, m_prefix.length()) == m_prefix;
+}
+
+bool CArgsParser::check_required_args(const CArgsParserResult& res)
+{
+ for (auto& it : m_args) {
+ if (it.value.required) {
+ if (!res.is_present(it.value.name))
+ return false;
+ }
+ }
+
+ int required_arguments = 0;
+ for (const auto& a : m_single_args) {
+ if (a.required) {
+ required_arguments++;
+ }
+ }
+
+ if (required_arguments != 0) {
+ if (res.m_single_values.size() < required_arguments)
+ return false;
+ }
+
+ return true;
+}
+
+void CArgsParser::add_required_arg(const String& name, const String& description)
+{
+ m_args.set(name, Arg(name, description, true));
+}
+
+void CArgsParser::add_required_arg(const String& name, const String& value_name, const String& description)
+{
+ m_args.set(name, Arg(name, value_name, description, true));
+}
+
+void CArgsParser::add_arg(const String& name, const String& description)
+{
+ m_args.set(name, Arg(name, description, false));
+}
+
+void CArgsParser::add_arg(const String& name, const String& value_name, const String& description)
+{
+ m_args.set(name, Arg(name, value_name, description, false));
+}
+
+void CArgsParser::add_single_value(const String& name)
+{
+ m_single_args.append(SingleArg { name, false });
+}
+
+void CArgsParser::add_required_single_value(const String& name)
+{
+ if (m_single_args.size() != 0) {
+ // adding required arguments after non-required arguments would be nonsensical
+ ASSERT(m_single_args.last().required);
+ }
+ m_single_args.append(SingleArg { name, true });
+}
+
+String CArgsParser::get_usage() const
+{
+ StringBuilder sb;
+
+ sb.append("usage : ");
+ sb.append(m_program_name);
+ sb.append(" ");
+
+ for (auto& it : m_args) {
+ if (it.value.required)
+ sb.append("<");
+ else
+ sb.append("[");
+
+ sb.append(m_prefix);
+ sb.append(it.value.name);
+
+ if (!it.value.value_name.is_null()) {
+ sb.append(" ");
+ sb.append(it.value.value_name);
+ }
+
+ if (it.value.required)
+ sb.append("> ");
+ else
+ sb.append("] ");
+ }
+
+ for (auto& arg : m_single_args) {
+ if (arg.required)
+ sb.append("<");
+ else
+ sb.append("[");
+
+ sb.append(arg.name);
+
+ if (arg.required)
+ sb.append("> ");
+ else
+ sb.append("] ");
+ }
+
+ sb.append("\n");
+
+ for (auto& it : m_args) {
+ sb.append(" ");
+ sb.append(m_prefix);
+ sb.append(it.value.name);
+
+ if (!it.value.value_name.is_null()) {
+ sb.append(" ");
+ sb.append(it.value.value_name);
+ }
+
+ sb.append(" : ");
+ sb.append(it.value.description);
+ sb.append("\n");
+ }
+
+ return sb.to_string();
+}
+
+void CArgsParser::print_usage() const
+{
+ printf("%s\n", get_usage().characters());
+}
diff --git a/Libraries/LibCore/CArgsParser.h b/Libraries/LibCore/CArgsParser.h
new file mode 100644
index 0000000000..6a696054e2
--- /dev/null
+++ b/Libraries/LibCore/CArgsParser.h
@@ -0,0 +1,69 @@
+#pragma once
+
+#include <AK/AKString.h>
+#include <AK/HashMap.h>
+#include <AK/Vector.h>
+
+/*
+ The class ArgsParser provides a way to parse arguments by using a given list that describes the possible
+ types of arguments (name, description, required or not, must be followed by a value...).
+ Call the add_arg() functions to describe your arguments.
+
+ The class ArgsParserResult is used to manipulate the arguments (checking if an arg has been provided,
+ retrieve its value...). In case of error (missing required argument) an empty structure is returned as result.
+*/
+
+class CArgsParserResult {
+public:
+ bool is_present(const String& arg_name) const;
+ String get(const String& arg_name) const;
+ const Vector<String>& get_single_values() const;
+
+private:
+ HashMap<String, String> m_args;
+ Vector<String> m_single_values;
+
+ friend class CArgsParser;
+};
+
+class CArgsParser {
+public:
+ CArgsParser(const String& program_name);
+
+ CArgsParserResult parse(int argc, char** argv);
+
+ void add_required_arg(const String& name, const String& description);
+ void add_required_arg(const String& name, const String& value_name, const String& description);
+ void add_arg(const String& name, const String& description);
+ void add_arg(const String& name, const String& value_name, const String& description);
+ void add_single_value(const String& name);
+ void add_required_single_value(const String& name);
+ String get_usage() const;
+ void print_usage() const;
+
+private:
+ struct Arg {
+ inline Arg() {}
+ Arg(const String& name, const String& description, bool required);
+ Arg(const String& name, const String& value_name, const String& description, bool required);
+
+ String name;
+ String description;
+ String value_name;
+ bool required;
+ };
+
+ int parse_next_param(int index, char** argv, const int params_left, CArgsParserResult& res);
+ bool is_param_valid(const String& param_name);
+ bool check_required_args(const CArgsParserResult& res);
+
+ String m_program_name;
+ String m_prefix;
+
+ struct SingleArg {
+ String name;
+ bool required;
+ };
+ Vector<SingleArg> m_single_args;
+ HashMap<String, Arg> m_args;
+};
diff --git a/Libraries/LibCore/CConfigFile.cpp b/Libraries/LibCore/CConfigFile.cpp
new file mode 100644
index 0000000000..2f1fec736a
--- /dev/null
+++ b/Libraries/LibCore/CConfigFile.cpp
@@ -0,0 +1,233 @@
+#include <AK/StringBuilder.h>
+#include <LibCore/CConfigFile.h>
+#include <LibCore/CFile.h>
+#include <LibCore/CUserInfo.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <unistd.h>
+
+NonnullRefPtr<CConfigFile> CConfigFile::get_for_app(const String& app_name)
+{
+ String home_path = get_current_user_home_path();
+ if (home_path == "/")
+ home_path = String::format("/tmp");
+ auto path = String::format("%s/%s.ini", home_path.characters(), app_name.characters());
+ return adopt(*new CConfigFile(path));
+}
+
+NonnullRefPtr<CConfigFile> CConfigFile::get_for_system(const String& app_name)
+{
+ auto path = String::format("/etc/%s.ini", app_name.characters());
+ return adopt(*new CConfigFile(path));
+}
+
+CConfigFile::CConfigFile(const String& file_name)
+ : m_file_name(file_name)
+{
+ reparse();
+}
+
+CConfigFile::~CConfigFile()
+{
+ sync();
+}
+
+void CConfigFile::reparse()
+{
+ m_groups.clear();
+
+ CFile file(m_file_name);
+ if (!file.open(CIODevice::OpenMode::ReadOnly))
+ return;
+
+ HashMap<String, String>* current_group = nullptr;
+
+ while (file.can_read_line()) {
+ auto line = file.read_line(BUFSIZ);
+ auto* cp = (const char*)line.pointer();
+
+ while (*cp && (*cp == ' ' || *cp == '\t' || *cp == '\n'))
+ ++cp;
+
+ switch (*cp) {
+ case '\0': // EOL...
+ case '#': // Comment, skip entire line.
+ case ';': // -||-
+ continue;
+ case '[': { // Start of new group.
+ StringBuilder builder;
+ ++cp; // Skip the '['
+ while (*cp && (*cp != ']'))
+ builder.append(*(cp++));
+ current_group = &m_groups.ensure(builder.to_string());
+ break;
+ }
+ default: { // Start of key{
+ StringBuilder key_builder;
+ StringBuilder value_builder;
+ while (*cp && (*cp != '='))
+ key_builder.append(*(cp++));
+ ++cp; // Skip the '='
+ while (*cp && (*cp != '\n'))
+ value_builder.append(*(cp++));
+ if (!current_group) {
+ // We're not in a group yet, create one with the name ""...
+ current_group = &m_groups.ensure("");
+ }
+ current_group->set(key_builder.to_string(), value_builder.to_string());
+ }
+ }
+ }
+}
+
+String CConfigFile::read_entry(const String& group, const String& key, const String& default_value) const
+{
+ if (!has_key(group, key)) {
+ const_cast<CConfigFile&>(*this).write_entry(group, key, default_value);
+ return default_value;
+ }
+ auto it = m_groups.find(group);
+ auto jt = it->value.find(key);
+ return jt->value;
+}
+
+int CConfigFile::read_num_entry(const String& group, const String& key, int default_value) const
+{
+ if (!has_key(group, key)) {
+ const_cast<CConfigFile&>(*this).write_num_entry(group, key, default_value);
+ return default_value;
+ }
+
+ bool ok;
+ int value = read_entry(group, key).to_uint(ok);
+ if (!ok)
+ return default_value;
+ return value;
+}
+
+Color CConfigFile::read_color_entry(const String& group, const String& key, Color default_value) const
+{
+ if (!has_key(group, key)) {
+ const_cast<CConfigFile&>(*this).write_color_entry(group, key, default_value);
+ return default_value;
+ }
+
+ auto shades = read_entry(group, key).split(',');
+ if (shades.size() < 3)
+ return default_value;
+ bool ok1 = true,
+ ok2 = true,
+ ok3 = true,
+ ok4 = true;
+ Color value;
+ if (shades.size() == 3) {
+ value = Color(shades[0].to_uint(ok1),
+ shades[1].to_uint(ok2),
+ shades[2].to_uint(ok3));
+ } else {
+ value = Color(shades[0].to_uint(ok1),
+ shades[1].to_uint(ok2),
+ shades[2].to_uint(ok3),
+ shades[3].to_uint(ok4));
+ }
+ if (!(ok1 && ok2 && ok3 && ok4))
+ return default_value;
+ return value;
+}
+
+bool CConfigFile::read_bool_entry(const String& group, const String& key, bool default_value) const
+{
+ return read_entry(group, key, default_value ? "1" : "0") == "1";
+}
+
+void CConfigFile::write_entry(const String& group, const String& key, const String& value)
+{
+ m_groups.ensure(group).ensure(key) = value;
+ m_dirty = true;
+}
+
+void CConfigFile::write_num_entry(const String& group, const String& key, int value)
+{
+ write_entry(group, key, String::number(value));
+}
+void CConfigFile::write_bool_entry(const String& group, const String& key, bool value)
+{
+ write_entry(group, key, value ? "1" : "0");
+}
+void CConfigFile::write_color_entry(const String& group, const String& key, Color value)
+{
+ write_entry(group, key, String::format("%d,%d,%d,%d", value.red(), value.green(), value.blue(), value.alpha()));
+}
+
+bool CConfigFile::sync()
+{
+ if (!m_dirty)
+ return true;
+
+ FILE* fp = fopen(m_file_name.characters(), "wb");
+ if (!fp)
+ return false;
+
+ for (auto& it : m_groups) {
+ fprintf(fp, "[%s]\n", it.key.characters());
+ for (auto& jt : it.value)
+ fprintf(fp, "%s=%s\n", jt.key.characters(), jt.value.characters());
+ fprintf(fp, "\n");
+ }
+
+ fclose(fp);
+
+ m_dirty = false;
+ return true;
+}
+
+void CConfigFile::dump() const
+{
+ for (auto& it : m_groups) {
+ printf("[%s]\n", it.key.characters());
+ for (auto& jt : it.value)
+ printf("%s=%s\n", jt.key.characters(), jt.value.characters());
+ printf("\n");
+ }
+}
+
+Vector<String> CConfigFile::groups() const
+{
+ return m_groups.keys();
+}
+
+Vector<String> CConfigFile::keys(const String& group) const
+{
+ auto it = m_groups.find(group);
+ if (it == m_groups.end())
+ return {};
+ return it->value.keys();
+}
+
+bool CConfigFile::has_key(const String& group, const String& key) const
+{
+ auto it = m_groups.find(group);
+ if (it == m_groups.end())
+ return {};
+ return it->value.contains(key);
+}
+
+bool CConfigFile::has_group(const String& group) const
+{
+ return m_groups.contains(group);
+}
+
+void CConfigFile::remove_group(const String& group)
+{
+ m_groups.remove(group);
+ m_dirty = true;
+}
+
+void CConfigFile::remove_entry(const String& group, const String& key)
+{
+ auto it = m_groups.find(group);
+ if (it == m_groups.end())
+ return;
+ it->value.remove(key);
+ m_dirty = true;
+}
diff --git a/Libraries/LibCore/CConfigFile.h b/Libraries/LibCore/CConfigFile.h
new file mode 100644
index 0000000000..1816ba53c9
--- /dev/null
+++ b/Libraries/LibCore/CConfigFile.h
@@ -0,0 +1,51 @@
+#pragma once
+
+#include <AK/AKString.h>
+#include <AK/HashMap.h>
+#include <AK/RefPtr.h>
+#include <AK/RefCounted.h>
+#include <AK/Vector.h>
+#include <SharedGraphics/Color.h>
+
+class CConfigFile : public RefCounted<CConfigFile> {
+public:
+ static NonnullRefPtr<CConfigFile> get_for_app(const String& app_name);
+ static NonnullRefPtr<CConfigFile> get_for_system(const String& app_name);
+ ~CConfigFile();
+
+ bool has_group(const String&) const;
+ bool has_key(const String& group, const String& key) const;
+
+ Vector<String> groups() const;
+ Vector<String> keys(const String& group) const;
+
+ String read_entry(const String& group, const String& key, const String& default_vaule = String()) const;
+ int read_num_entry(const String& group, const String& key, int default_value = 0) const;
+ bool read_bool_entry(const String& group, const String& key, bool default_value = false) const;
+ Color read_color_entry(const String& group, const String& key, Color default_value) const;
+
+ void write_entry(const String& group, const String& key, const String& value);
+ void write_num_entry(const String& group, const String& key, int value);
+ void write_bool_entry(const String& group, const String& key, bool value);
+ void write_color_entry(const String& group, const String& key, Color value);
+
+ void dump() const;
+
+ bool is_dirty() const { return m_dirty; }
+
+ bool sync();
+
+ void remove_group(const String& group);
+ void remove_entry(const String& group, const String& key);
+
+ String file_name() const { return m_file_name; }
+
+private:
+ explicit CConfigFile(const String& file_name);
+
+ void reparse();
+
+ String m_file_name;
+ HashMap<String, HashMap<String, String>> m_groups;
+ bool m_dirty { false };
+};
diff --git a/Libraries/LibCore/CDirIterator.cpp b/Libraries/LibCore/CDirIterator.cpp
new file mode 100644
index 0000000000..d76d52970d
--- /dev/null
+++ b/Libraries/LibCore/CDirIterator.cpp
@@ -0,0 +1,67 @@
+#include "CDirIterator.h"
+#include <cerrno>
+
+CDirIterator::CDirIterator(const StringView& path, Flags flags)
+ : m_flags(flags)
+{
+ m_dir = opendir(path.characters());
+ if (m_dir == nullptr) {
+ m_error = errno;
+ }
+}
+
+CDirIterator::~CDirIterator()
+{
+ if (m_dir != nullptr) {
+ closedir(m_dir);
+ m_dir = nullptr;
+ }
+}
+
+bool CDirIterator::advance_next()
+{
+ if (m_dir == nullptr)
+ return false;
+
+ bool keep_advancing = true;
+ while (keep_advancing) {
+ errno = 0;
+ auto* de = readdir(m_dir);
+ if (de) {
+ m_next = de->d_name;
+ } else {
+ m_error = errno;
+ m_next = String();
+ }
+
+ if (m_next.is_null()) {
+ keep_advancing = false;
+ } else if (m_flags & Flags::SkipDots) {
+ if (m_next.length() < 1 || m_next[0] != '.') {
+ keep_advancing = false;
+ }
+ } else {
+ keep_advancing = false;
+ }
+ }
+
+ return m_next.length() > 0;
+}
+
+bool CDirIterator::has_next()
+{
+ if (!m_next.is_null())
+ return true;
+
+ return advance_next();
+}
+
+String CDirIterator::next_path()
+{
+ if (m_next.is_null())
+ advance_next();
+
+ auto tmp = m_next;
+ m_next = String();
+ return tmp;
+}
diff --git a/Libraries/LibCore/CDirIterator.h b/Libraries/LibCore/CDirIterator.h
new file mode 100644
index 0000000000..08c9040b48
--- /dev/null
+++ b/Libraries/LibCore/CDirIterator.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <AK/AKString.h>
+#include <dirent.h>
+
+class CDirIterator {
+public:
+ enum Flags {
+ NoFlags = 0x0,
+ SkipDots = 0x1,
+ };
+
+ CDirIterator(const StringView& path, Flags = Flags::NoFlags);
+ ~CDirIterator();
+
+ bool has_error() const { return m_error != 0; }
+ int error() const { return m_error; }
+ const char* error_string() const { return strerror(m_error); }
+ bool has_next();
+ String next_path();
+
+private:
+ DIR* m_dir = nullptr;
+ int m_error = 0;
+ String m_next;
+ int m_flags;
+
+ bool advance_next();
+};
diff --git a/Libraries/LibCore/CElapsedTimer.cpp b/Libraries/LibCore/CElapsedTimer.cpp
new file mode 100644
index 0000000000..4e8902d348
--- /dev/null
+++ b/Libraries/LibCore/CElapsedTimer.cpp
@@ -0,0 +1,20 @@
+#include <AK/Assertions.h>
+#include <AK/Time.h>
+#include <LibCore/CElapsedTimer.h>
+#include <sys/time.h>
+
+void CElapsedTimer::start()
+{
+ m_valid = true;
+ gettimeofday(&m_start_time, nullptr);
+}
+
+int CElapsedTimer::elapsed() const
+{
+ ASSERT(is_valid());
+ struct timeval now;
+ gettimeofday(&now, nullptr);
+ struct timeval diff;
+ timeval_sub(now, m_start_time, diff);
+ return diff.tv_sec * 1000 + diff.tv_usec / 1000;
+}
diff --git a/Libraries/LibCore/CElapsedTimer.h b/Libraries/LibCore/CElapsedTimer.h
new file mode 100644
index 0000000000..76fa304478
--- /dev/null
+++ b/Libraries/LibCore/CElapsedTimer.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <sys/time.h>
+
+class CElapsedTimer {
+public:
+ CElapsedTimer() {}
+
+ bool is_valid() const { return m_valid; }
+ void start();
+ int elapsed() const;
+
+private:
+ bool m_valid { false };
+ struct timeval m_start_time {
+ 0, 0
+ };
+};
diff --git a/Libraries/LibCore/CEvent.cpp b/Libraries/LibCore/CEvent.cpp
new file mode 100644
index 0000000000..eacce964c2
--- /dev/null
+++ b/Libraries/LibCore/CEvent.cpp
@@ -0,0 +1,12 @@
+#include <LibCore/CEvent.h>
+#include <LibCore/CObject.h>
+
+CChildEvent::CChildEvent(Type type, CObject& child)
+ : CEvent(type)
+ , m_child(child.make_weak_ptr())
+{
+}
+
+CChildEvent::~CChildEvent()
+{
+}
diff --git a/Libraries/LibCore/CEvent.h b/Libraries/LibCore/CEvent.h
new file mode 100644
index 0000000000..9d262c4a76
--- /dev/null
+++ b/Libraries/LibCore/CEvent.h
@@ -0,0 +1,74 @@
+#pragma once
+
+#include <AK/AKString.h>
+#include <AK/Function.h>
+#include <AK/Types.h>
+#include <AK/WeakPtr.h>
+
+class CObject;
+
+class CEvent {
+public:
+ enum Type {
+ Invalid = 0,
+ Quit,
+ Timer,
+ DeferredDestroy,
+ DeferredInvoke,
+ ChildAdded,
+ ChildRemoved,
+ };
+
+ CEvent() {}
+ explicit CEvent(unsigned type)
+ : m_type(type)
+ {
+ }
+ virtual ~CEvent() {}
+
+ unsigned type() const { return m_type; }
+
+private:
+ unsigned m_type { Type::Invalid };
+};
+
+class CDeferredInvocationEvent : public CEvent {
+ friend class CEventLoop;
+
+public:
+ CDeferredInvocationEvent(Function<void(CObject&)> invokee)
+ : CEvent(CEvent::Type::DeferredInvoke)
+ , m_invokee(move(invokee))
+ {
+ }
+
+private:
+ Function<void(CObject&)> m_invokee;
+};
+
+class CTimerEvent final : public CEvent {
+public:
+ explicit CTimerEvent(int timer_id)
+ : CEvent(CEvent::Timer)
+ , m_timer_id(timer_id)
+ {
+ }
+ ~CTimerEvent() {}
+
+ int timer_id() const { return m_timer_id; }
+
+private:
+ int m_timer_id;
+};
+
+class CChildEvent final : public CEvent {
+public:
+ CChildEvent(Type, CObject& child);
+ ~CChildEvent();
+
+ CObject* child() { return m_child.ptr(); }
+ const CObject* child() const { return m_child.ptr(); }
+
+private:
+ WeakPtr<CObject> m_child;
+};
diff --git a/Libraries/LibCore/CEventLoop.cpp b/Libraries/LibCore/CEventLoop.cpp
new file mode 100644
index 0000000000..e38c2b2154
--- /dev/null
+++ b/Libraries/LibCore/CEventLoop.cpp
@@ -0,0 +1,302 @@
+#include <AK/Time.h>
+#include <LibCore/CEvent.h>
+#include <LibCore/CEventLoop.h>
+#include <LibCore/CLock.h>
+#include <LibCore/CNotifier.h>
+#include <LibCore/CObject.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <time.h>
+#include <unistd.h>
+
+//#define CEVENTLOOP_DEBUG
+//#define DEFERRED_INVOKE_DEBUG
+
+static CEventLoop* s_main_event_loop;
+static Vector<CEventLoop*>* s_event_loop_stack;
+HashMap<int, OwnPtr<CEventLoop::EventLoopTimer>>* CEventLoop::s_timers;
+HashTable<CNotifier*>* CEventLoop::s_notifiers;
+int CEventLoop::s_next_timer_id = 1;
+
+CEventLoop::CEventLoop()
+{
+ if (!s_event_loop_stack) {
+ s_event_loop_stack = new Vector<CEventLoop*>;
+ s_timers = new HashMap<int, OwnPtr<CEventLoop::EventLoopTimer>>;
+ s_notifiers = new HashTable<CNotifier*>;
+ }
+
+ if (!s_main_event_loop) {
+ s_main_event_loop = this;
+ s_event_loop_stack->append(this);
+ }
+
+#ifdef CEVENTLOOP_DEBUG
+ dbgprintf("(%u) CEventLoop constructed :)\n", getpid());
+#endif
+}
+
+CEventLoop::~CEventLoop()
+{
+}
+
+CEventLoop& CEventLoop::main()
+{
+ ASSERT(s_main_event_loop);
+ return *s_main_event_loop;
+}
+
+CEventLoop& CEventLoop::current()
+{
+ return *s_event_loop_stack->last();
+}
+
+void CEventLoop::quit(int code)
+{
+ m_exit_requested = true;
+ m_exit_code = code;
+}
+
+struct CEventLoopPusher {
+public:
+ CEventLoopPusher(CEventLoop& event_loop)
+ : m_event_loop(event_loop)
+ {
+ if (&m_event_loop != s_main_event_loop) {
+ m_event_loop.take_pending_events_from(CEventLoop::current());
+ s_event_loop_stack->append(&event_loop);
+ }
+ }
+ ~CEventLoopPusher()
+ {
+ if (&m_event_loop != s_main_event_loop) {
+ s_event_loop_stack->take_last();
+ CEventLoop::current().take_pending_events_from(m_event_loop);
+ }
+ }
+
+private:
+ CEventLoop& m_event_loop;
+};
+
+int CEventLoop::exec()
+{
+ CEventLoopPusher pusher(*this);
+ for (;;) {
+ if (m_exit_requested)
+ return m_exit_code;
+ pump();
+ }
+ ASSERT_NOT_REACHED();
+}
+
+void CEventLoop::pump(WaitMode mode)
+{
+ // window server event processing...
+ do_processing();
+
+ if (m_queued_events.is_empty()) {
+ wait_for_event(mode);
+ do_processing();
+ }
+ decltype(m_queued_events) events;
+ {
+ LOCKER(m_lock);
+ events = move(m_queued_events);
+ }
+
+ for (auto& queued_event : events) {
+ auto* receiver = queued_event.receiver.ptr();
+ auto& event = *queued_event.event;
+#ifdef CEVENTLOOP_DEBUG
+ dbgprintf("CEventLoop: %s{%p} event %u\n", receiver->class_name(), receiver, (unsigned)event.type());
+#endif
+ if (!receiver) {
+ switch (event.type()) {
+ case CEvent::Quit:
+ ASSERT_NOT_REACHED();
+ return;
+ default:
+ dbgprintf("Event type %u with no receiver :(\n", event.type());
+ }
+ } else if (event.type() == CEvent::Type::DeferredInvoke) {
+#ifdef DEFERRED_INVOKE_DEBUG
+ printf("DeferredInvoke: receiver=%s{%p}\n", receiver->class_name(), receiver);
+#endif
+ static_cast<CDeferredInvocationEvent&>(event).m_invokee(*receiver);
+ } else {
+ receiver->event(event);
+ }
+
+ if (m_exit_requested) {
+ LOCKER(m_lock);
+ auto rejigged_event_queue = move(events);
+ rejigged_event_queue.append(move(m_queued_events));
+ m_queued_events = move(rejigged_event_queue);
+ }
+ }
+}
+
+void CEventLoop::post_event(CObject& receiver, OwnPtr<CEvent>&& event)
+{
+ LOCKER(m_lock);
+#ifdef CEVENTLOOP_DEBUG
+ dbgprintf("CEventLoop::post_event: {%u} << receiver=%p, event=%p\n", m_queued_events.size(), &receiver, event.ptr());
+#endif
+ m_queued_events.append({ receiver.make_weak_ptr(), move(event) });
+}
+
+void CEventLoop::wait_for_event(WaitMode mode)
+{
+ fd_set rfds;
+ fd_set wfds;
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+
+ int max_fd = 0;
+ auto add_fd_to_set = [&max_fd](int fd, fd_set& set) {
+ FD_SET(fd, &set);
+ if (fd > max_fd)
+ max_fd = fd;
+ };
+
+ int max_fd_added = -1;
+ add_file_descriptors_for_select(rfds, max_fd_added);
+ max_fd = max(max_fd, max_fd_added);
+ for (auto& notifier : *s_notifiers) {
+ if (notifier->event_mask() & CNotifier::Read)
+ add_fd_to_set(notifier->fd(), rfds);
+ if (notifier->event_mask() & CNotifier::Write)
+ add_fd_to_set(notifier->fd(), wfds);
+ if (notifier->event_mask() & CNotifier::Exceptional)
+ ASSERT_NOT_REACHED();
+ }
+
+ bool queued_events_is_empty;
+ {
+ LOCKER(m_lock);
+ queued_events_is_empty = m_queued_events.is_empty();
+ }
+
+ timeval now;
+ struct timeval timeout = { 0, 0 };
+ bool should_wait_forever = false;
+ if (mode == WaitMode::WaitForEvents) {
+ if (!s_timers->is_empty() && queued_events_is_empty) {
+ gettimeofday(&now, nullptr);
+ get_next_timer_expiration(timeout);
+ timeval_sub(timeout, now, timeout);
+ } else {
+ should_wait_forever = true;
+ }
+ } else {
+ should_wait_forever = false;
+ }
+
+ int marked_fd_count = select(max_fd + 1, &rfds, &wfds, nullptr, should_wait_forever ? nullptr : &timeout);
+ if (marked_fd_count < 0) {
+ ASSERT_NOT_REACHED();
+ }
+
+ if (!s_timers->is_empty()) {
+ gettimeofday(&now, nullptr);
+ }
+
+ for (auto& it : *s_timers) {
+ auto& timer = *it.value;
+ if (!timer.has_expired(now))
+ continue;
+#ifdef CEVENTLOOP_DEBUG
+ dbgprintf("CEventLoop: Timer %d has expired, sending CTimerEvent to %p\n", timer.timer_id, timer.owner);
+#endif
+ post_event(*timer.owner, make<CTimerEvent>(timer.timer_id));
+ if (timer.should_reload) {
+ timer.reload(now);
+ } else {
+ // FIXME: Support removing expired timers that don't want to reload.
+ ASSERT_NOT_REACHED();
+ }
+ }
+
+ if (!marked_fd_count)
+ return;
+
+ for (auto& notifier : *s_notifiers) {
+ if (FD_ISSET(notifier->fd(), &rfds)) {
+ if (notifier->on_ready_to_read)
+ notifier->on_ready_to_read();
+ }
+ if (FD_ISSET(notifier->fd(), &wfds)) {
+ if (notifier->on_ready_to_write)
+ notifier->on_ready_to_write();
+ }
+ }
+
+ process_file_descriptors_after_select(rfds);
+}
+
+bool CEventLoop::EventLoopTimer::has_expired(const timeval& now) const
+{
+ return now.tv_sec > fire_time.tv_sec || (now.tv_sec == fire_time.tv_sec && now.tv_usec >= fire_time.tv_usec);
+}
+
+void CEventLoop::EventLoopTimer::reload(const timeval& now)
+{
+ fire_time = now;
+ fire_time.tv_sec += interval / 1000;
+ fire_time.tv_usec += (interval % 1000) * 1000;
+}
+
+void CEventLoop::get_next_timer_expiration(timeval& soonest)
+{
+ ASSERT(!s_timers->is_empty());
+ bool has_checked_any = false;
+ for (auto& it : *s_timers) {
+ auto& fire_time = it.value->fire_time;
+ if (!has_checked_any || fire_time.tv_sec < soonest.tv_sec || (fire_time.tv_sec == soonest.tv_sec && fire_time.tv_usec < soonest.tv_usec))
+ soonest = fire_time;
+ has_checked_any = true;
+ }
+}
+
+int CEventLoop::register_timer(CObject& object, int milliseconds, bool should_reload)
+{
+ ASSERT(milliseconds >= 0);
+ auto timer = make<EventLoopTimer>();
+ timer->owner = object.make_weak_ptr();
+ timer->interval = milliseconds;
+ timeval now;
+ gettimeofday(&now, nullptr);
+ timer->reload(now);
+ timer->should_reload = should_reload;
+ int timer_id = ++s_next_timer_id; // FIXME: This will eventually wrap around.
+ ASSERT(timer_id); // FIXME: Aforementioned wraparound.
+ timer->timer_id = timer_id;
+ s_timers->set(timer->timer_id, move(timer));
+ return timer_id;
+}
+
+bool CEventLoop::unregister_timer(int timer_id)
+{
+ auto it = s_timers->find(timer_id);
+ if (it == s_timers->end())
+ return false;
+ s_timers->remove(it);
+ return true;
+}
+
+void CEventLoop::register_notifier(Badge<CNotifier>, CNotifier& notifier)
+{
+ s_notifiers->set(&notifier);
+}
+
+void CEventLoop::unregister_notifier(Badge<CNotifier>, CNotifier& notifier)
+{
+ s_notifiers->remove(&notifier);
+}
diff --git a/Libraries/LibCore/CEventLoop.h b/Libraries/LibCore/CEventLoop.h
new file mode 100644
index 0000000000..8cab0bbe99
--- /dev/null
+++ b/Libraries/LibCore/CEventLoop.h
@@ -0,0 +1,89 @@
+#pragma once
+
+#include <AK/Badge.h>
+#include <AK/HashMap.h>
+#include <AK/OwnPtr.h>
+#include <AK/Vector.h>
+#include <AK/WeakPtr.h>
+#include <LibCore/CLock.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <time.h>
+
+class CEvent;
+class CObject;
+class CNotifier;
+
+class CEventLoop {
+public:
+ CEventLoop();
+ virtual ~CEventLoop();
+
+ int exec();
+
+ enum class WaitMode {
+ WaitForEvents,
+ PollForEvents,
+ };
+
+ // processe events, generally called by exec() in a loop.
+ // this should really only be used for integrating with other event loops
+ void pump(WaitMode = WaitMode::WaitForEvents);
+
+ void post_event(CObject& receiver, OwnPtr<CEvent>&&);
+
+ static CEventLoop& main();
+ static CEventLoop& current();
+
+ bool was_exit_requested() const { return m_exit_requested; }
+
+ static int register_timer(CObject&, int milliseconds, bool should_reload);
+ static bool unregister_timer(int timer_id);
+
+ static void register_notifier(Badge<CNotifier>, CNotifier&);
+ static void unregister_notifier(Badge<CNotifier>, CNotifier&);
+
+ void quit(int);
+
+ virtual void take_pending_events_from(CEventLoop& other)
+ {
+ m_queued_events.append(move(other.m_queued_events));
+ }
+
+protected:
+ virtual void add_file_descriptors_for_select(fd_set&, int& max_fd) { UNUSED_PARAM(max_fd); }
+ virtual void process_file_descriptors_after_select(const fd_set&) {}
+ virtual void do_processing() {}
+
+private:
+ void wait_for_event(WaitMode);
+ void get_next_timer_expiration(timeval&);
+
+ struct QueuedEvent {
+ WeakPtr<CObject> receiver;
+ OwnPtr<CEvent> event;
+ };
+
+ Vector<QueuedEvent, 64> m_queued_events;
+
+ bool m_exit_requested { false };
+ int m_exit_code { 0 };
+
+ CLock m_lock;
+
+ struct EventLoopTimer {
+ int timer_id { 0 };
+ int interval { 0 };
+ timeval fire_time;
+ bool should_reload { false };
+ WeakPtr<CObject> owner;
+
+ void reload(const timeval& now);
+ bool has_expired(const timeval& now) const;
+ };
+
+ static HashMap<int, OwnPtr<EventLoopTimer>>* s_timers;
+ static int s_next_timer_id;
+
+ static HashTable<CNotifier*>* s_notifiers;
+};
diff --git a/Libraries/LibCore/CFile.cpp b/Libraries/LibCore/CFile.cpp
new file mode 100644
index 0000000000..09d209d4b4
--- /dev/null
+++ b/Libraries/LibCore/CFile.cpp
@@ -0,0 +1,51 @@
+#include <LibCore/CFile.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+
+CFile::CFile(const StringView& filename)
+ : m_filename(filename)
+{
+}
+
+CFile::~CFile()
+{
+ if (m_should_close_file_descriptor == ShouldCloseFileDescription::Yes && mode() != NotOpen)
+ close();
+}
+
+bool CFile::open(int fd, CIODevice::OpenMode mode, ShouldCloseFileDescription should_close)
+{
+ set_fd(fd);
+ set_mode(mode);
+ m_should_close_file_descriptor = should_close;
+ return true;
+}
+
+bool CFile::open(CIODevice::OpenMode mode)
+{
+ int flags = 0;
+ if ((mode & CIODevice::ReadWrite) == CIODevice::ReadWrite) {
+ flags |= O_RDWR | O_CREAT;
+ } else if (mode & CIODevice::ReadOnly) {
+ flags |= O_RDONLY;
+ } else if (mode & CIODevice::WriteOnly) {
+ flags |= O_WRONLY | O_CREAT;
+ }
+ if (mode & CIODevice::Append)
+ flags |= O_APPEND;
+ if (mode & CIODevice::Truncate)
+ flags |= O_TRUNC;
+ if (mode & CIODevice::MustBeNew)
+ flags |= O_EXCL;
+ int fd = ::open(m_filename.characters(), flags, 0666);
+ if (fd < 0) {
+ set_error(errno);
+ return false;
+ }
+
+ set_fd(fd);
+ set_mode(mode);
+ return true;
+}
diff --git a/Libraries/LibCore/CFile.h b/Libraries/LibCore/CFile.h
new file mode 100644
index 0000000000..976d5718d9
--- /dev/null
+++ b/Libraries/LibCore/CFile.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include <AK/AKString.h>
+#include <LibCore/CIODevice.h>
+
+class CFile final : public CIODevice {
+public:
+ CFile() {}
+ explicit CFile(const StringView&);
+ virtual ~CFile() override;
+
+ String filename() const { return m_filename; }
+ void set_filename(const StringView& filename) { m_filename = filename; }
+
+ virtual bool open(CIODevice::OpenMode) override;
+
+ enum class ShouldCloseFileDescription {
+ No = 0,
+ Yes
+ };
+ bool open(int fd, CIODevice::OpenMode, ShouldCloseFileDescription);
+
+ virtual const char* class_name() const override { return "CFile"; }
+
+private:
+ String m_filename;
+ ShouldCloseFileDescription m_should_close_file_descriptor { ShouldCloseFileDescription::Yes };
+};
diff --git a/Libraries/LibCore/CHttpJob.cpp b/Libraries/LibCore/CHttpJob.cpp
new file mode 100644
index 0000000000..a3b00b9175
--- /dev/null
+++ b/Libraries/LibCore/CHttpJob.cpp
@@ -0,0 +1,118 @@
+#include <LibCore/CHttpJob.h>
+#include <LibCore/CHttpResponse.h>
+#include <LibCore/CTCPSocket.h>
+#include <stdio.h>
+#include <unistd.h>
+
+CHttpJob::CHttpJob(const CHttpRequest& request)
+ : m_request(request)
+{
+}
+
+CHttpJob::~CHttpJob()
+{
+}
+
+void CHttpJob::on_socket_connected()
+{
+ auto raw_request = m_request.to_raw_request();
+#if 0
+ printf("raw_request:\n%s\n", String::copy(raw_request).characters());
+#endif
+
+ bool success = m_socket->send(raw_request);
+ if (!success)
+ return deferred_invoke([this](auto&) { did_fail(CNetworkJob::Error::TransmissionFailed); });
+
+ Vector<u8> buffer;
+ while (m_socket->is_connected()) {
+ if (m_state == State::InStatus) {
+ while (!m_socket->can_read_line())
+ usleep(1);
+ ASSERT(m_socket->can_read_line());
+ auto line = m_socket->read_line(PAGE_SIZE);
+ if (line.is_null()) {
+ printf("Expected HTTP status\n");
+ return deferred_invoke([this](auto&) { did_fail(CNetworkJob::Error::TransmissionFailed); });
+ }
+ auto parts = String::copy(line, Chomp).split(' ');
+ if (parts.size() < 3) {
+ printf("Expected 3-part HTTP status, got '%s'\n", line.pointer());
+ return deferred_invoke([this](auto&) { did_fail(CNetworkJob::Error::ProtocolFailed); });
+ }
+ bool ok;
+ m_code = parts[1].to_uint(ok);
+ if (!ok) {
+ printf("Expected numeric HTTP status\n");
+ return deferred_invoke([this](auto&) { did_fail(CNetworkJob::Error::ProtocolFailed); });
+ }
+ m_state = State::InHeaders;
+ continue;
+ }
+ if (m_state == State::InHeaders) {
+ while (!m_socket->can_read_line())
+ usleep(1);
+ auto line = m_socket->read_line(PAGE_SIZE);
+ if (line.is_null()) {
+ printf("Expected HTTP header\n");
+ return did_fail(CNetworkJob::Error::ProtocolFailed);
+ }
+ auto chomped_line = String::copy(line, Chomp);
+ if (chomped_line.is_empty()) {
+ m_state = State::InBody;
+ continue;
+ }
+ auto parts = chomped_line.split(':');
+ if (parts.is_empty()) {
+ printf("Expected HTTP header with key/value\n");
+ return deferred_invoke([this](auto&) { did_fail(CNetworkJob::Error::ProtocolFailed); });
+ }
+ auto name = parts[0];
+ if (chomped_line.length() < name.length() + 2) {
+ printf("Malformed HTTP header: '%s' (%d)\n", chomped_line.characters(), chomped_line.length());
+ return deferred_invoke([this](auto&) { did_fail(CNetworkJob::Error::ProtocolFailed); });
+ }
+ auto value = chomped_line.substring(name.length() + 2, chomped_line.length() - name.length() - 2);
+ m_headers.set(name, value);
+ printf("[%s] = '%s'\n", name.characters(), value.characters());
+ continue;
+ }
+ ASSERT(m_state == State::InBody);
+ while (!m_socket->can_read())
+ usleep(1);
+ ASSERT(m_socket->can_read());
+ auto payload = m_socket->receive(PAGE_SIZE);
+ if (!payload) {
+ if (m_socket->eof()) {
+ m_state = State::Finished;
+ break;
+ }
+ return deferred_invoke([this](auto&) { did_fail(CNetworkJob::Error::ProtocolFailed); });
+ }
+ buffer.append(payload.pointer(), payload.size());
+
+ bool ok;
+ if (buffer.size() >= m_headers.get("Content-Length").to_int(ok) && ok) {
+ m_state = State::Finished;
+ break;
+ }
+ }
+
+ auto response = CHttpResponse::create(m_code, move(m_headers), ByteBuffer::copy(buffer.data(), buffer.size()));
+ deferred_invoke([this, response](auto&) {
+ did_finish(move(response));
+ });
+}
+
+void CHttpJob::start()
+{
+ ASSERT(!m_socket);
+ m_socket = new CTCPSocket(this);
+ m_socket->on_connected = [this] {
+ printf("Socket on_connected callback\n");
+ on_socket_connected();
+ };
+ bool success = m_socket->connect(m_request.hostname(), m_request.port());
+ if (!success)
+ return did_fail(CNetworkJob::Error::ConnectionFailed);
+}
diff --git a/Libraries/LibCore/CHttpJob.h b/Libraries/LibCore/CHttpJob.h
new file mode 100644
index 0000000000..5b714a7f77
--- /dev/null
+++ b/Libraries/LibCore/CHttpJob.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <AK/HashMap.h>
+#include <LibCore/CHttpRequest.h>
+#include <LibCore/CNetworkJob.h>
+
+class CTCPSocket;
+
+class CHttpJob final : public CNetworkJob {
+public:
+ explicit CHttpJob(const CHttpRequest&);
+ virtual ~CHttpJob() override;
+
+ virtual void start() override;
+
+ virtual const char* class_name() const override { return "CHttpJob"; }
+
+private:
+ void on_socket_connected();
+
+ enum class State {
+ InStatus,
+ InHeaders,
+ InBody,
+ Finished,
+ };
+
+ CHttpRequest m_request;
+ CTCPSocket* m_socket { nullptr };
+ State m_state { State::InStatus };
+ int m_code { -1 };
+ HashMap<String, String> m_headers;
+};
diff --git a/Libraries/LibCore/CHttpRequest.cpp b/Libraries/LibCore/CHttpRequest.cpp
new file mode 100644
index 0000000000..93cbc6987d
--- /dev/null
+++ b/Libraries/LibCore/CHttpRequest.cpp
@@ -0,0 +1,44 @@
+#include <AK/StringBuilder.h>
+#include <LibCore/CHttpJob.h>
+#include <LibCore/CHttpRequest.h>
+
+CHttpRequest::CHttpRequest()
+{
+}
+
+CHttpRequest::~CHttpRequest()
+{
+}
+
+CNetworkJob* CHttpRequest::schedule()
+{
+ auto* job = new CHttpJob(*this);
+ job->start();
+ return job;
+}
+
+String CHttpRequest::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 CHttpRequest::to_raw_request() const
+{
+ StringBuilder builder;
+ builder.append(method_name());
+ builder.append(' ');
+ builder.append(m_path);
+ builder.append(" HTTP/1.0\r\nHost: ");
+ builder.append(m_hostname);
+ builder.append("\r\n\r\n");
+ return builder.to_byte_buffer();
+}
diff --git a/Libraries/LibCore/CHttpRequest.h b/Libraries/LibCore/CHttpRequest.h
new file mode 100644
index 0000000000..c5a39c9ffd
--- /dev/null
+++ b/Libraries/LibCore/CHttpRequest.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include <AK/AKString.h>
+
+class CNetworkJob;
+
+class CHttpRequest {
+public:
+ enum Method {
+ Invalid,
+ HEAD,
+ GET,
+ POST
+ };
+
+ CHttpRequest();
+ ~CHttpRequest();
+
+ 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;
+
+ CNetworkJob* schedule();
+
+private:
+ String m_hostname;
+ String m_path;
+ int m_port { 80 };
+ Method m_method { GET };
+};
diff --git a/Libraries/LibCore/CHttpResponse.cpp b/Libraries/LibCore/CHttpResponse.cpp
new file mode 100644
index 0000000000..07fef16ee0
--- /dev/null
+++ b/Libraries/LibCore/CHttpResponse.cpp
@@ -0,0 +1,12 @@
+#include <LibCore/CHttpResponse.h>
+
+CHttpResponse::CHttpResponse(int code, HashMap<String, String>&& headers, ByteBuffer&& payload)
+ : CNetworkResponse(move(payload))
+ , m_code(code)
+ , m_headers(move(headers))
+{
+}
+
+CHttpResponse::~CHttpResponse()
+{
+}
diff --git a/Libraries/LibCore/CHttpResponse.h b/Libraries/LibCore/CHttpResponse.h
new file mode 100644
index 0000000000..3feaa6bfd2
--- /dev/null
+++ b/Libraries/LibCore/CHttpResponse.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <AK/AKString.h>
+#include <AK/HashMap.h>
+#include <LibCore/CNetworkResponse.h>
+
+class CHttpResponse : public CNetworkResponse {
+public:
+ virtual ~CHttpResponse() override;
+ static NonnullRefPtr<CHttpResponse> create(int code, HashMap<String, String>&& headers, ByteBuffer&& payload)
+ {
+ return adopt(*new CHttpResponse(code, move(headers), move(payload)));
+ }
+
+ int code() const { return m_code; }
+ const HashMap<String, String>& headers() const { return m_headers; }
+
+private:
+ CHttpResponse(int code, HashMap<String, String>&&, ByteBuffer&&);
+
+ int m_code { 0 };
+ HashMap<String, String> m_headers;
+};
diff --git a/Libraries/LibCore/CIODevice.cpp b/Libraries/LibCore/CIODevice.cpp
new file mode 100644
index 0000000000..86393f6888
--- /dev/null
+++ b/Libraries/LibCore/CIODevice.cpp
@@ -0,0 +1,242 @@
+#include <AK/PrintfImplementation.h>
+#include <LibCore/CIODevice.h>
+#include <errno.h>
+#include <stdio.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+CIODevice::CIODevice(CObject* parent)
+ : CObject(parent)
+{
+}
+
+CIODevice::~CIODevice()
+{
+}
+
+const char* CIODevice::error_string() const
+{
+ return strerror(m_error);
+}
+
+ByteBuffer CIODevice::read(int max_size)
+{
+ if (m_fd < 0)
+ return {};
+ if (!max_size)
+ return {};
+ auto buffer = ByteBuffer::create_uninitialized(max_size);
+ auto* buffer_ptr = (char*)buffer.pointer();
+ int remaining_buffer_space = buffer.size();
+ int taken_from_buffered = 0;
+ if (!m_buffered_data.is_empty()) {
+ taken_from_buffered = min(remaining_buffer_space, m_buffered_data.size());
+ memcpy(buffer_ptr, m_buffered_data.data(), taken_from_buffered);
+ Vector<u8> new_buffered_data;
+ new_buffered_data.append(m_buffered_data.data() + taken_from_buffered, m_buffered_data.size() - taken_from_buffered);
+ m_buffered_data = move(new_buffered_data);
+ remaining_buffer_space -= taken_from_buffered;
+ buffer_ptr += taken_from_buffered;
+ }
+ if (!remaining_buffer_space)
+ return buffer;
+ int nread = ::read(m_fd, buffer_ptr, remaining_buffer_space);
+ if (nread < 0) {
+ if (taken_from_buffered) {
+ buffer.trim(taken_from_buffered);
+ return buffer;
+ }
+ set_error(errno);
+ return {};
+ }
+ if (nread == 0) {
+ set_eof(true);
+ if (taken_from_buffered) {
+ buffer.trim(taken_from_buffered);
+ return buffer;
+ }
+ return {};
+ }
+ buffer.trim(taken_from_buffered + nread);
+ return buffer;
+}
+
+bool CIODevice::can_read_from_fd() const
+{
+ // FIXME: Can we somehow remove this once CSocket 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("CIODevice::can_read: select");
+ return false;
+ }
+ return FD_ISSET(m_fd, &rfds);
+}
+
+bool CIODevice::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_from_fd())
+ return false;
+ populate_read_buffer();
+ return m_buffered_data.contains_slow('\n');
+}
+
+bool CIODevice::can_read() const
+{
+ return !m_buffered_data.is_empty() || can_read_from_fd();
+}
+
+ByteBuffer CIODevice::read_all()
+{
+ ByteBuffer buffer;
+ if (!m_buffered_data.is_empty()) {
+ buffer = ByteBuffer::copy(m_buffered_data.data(), m_buffered_data.size());
+ m_buffered_data.clear();
+ }
+
+ while (can_read_from_fd()) {
+ char read_buffer[4096];
+ int nread = ::read(m_fd, read_buffer, sizeof(read_buffer));
+ if (nread < 0) {
+ set_error(nread);
+ return buffer;
+ }
+ if (nread == 0) {
+ set_eof(true);
+ break;
+ }
+ buffer.append(read_buffer, nread);
+ }
+ return buffer;
+}
+
+ByteBuffer CIODevice::read_line(int max_size)
+{
+ if (m_fd < 0)
+ return {};
+ if (!max_size)
+ return {};
+ if (!can_read_line())
+ return {};
+ if (m_eof) {
+ if (m_buffered_data.size() > max_size) {
+ dbgprintf("CIODevice::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) {
+ u8 ch = m_buffered_data[line_index];
+ line[line_index++] = ch;
+ if (ch == '\n') {
+ Vector<u8> 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[line_index] = '\0';
+ line.trim(line_index + 1);
+ return line;
+ }
+ }
+ return {};
+}
+
+bool CIODevice::populate_read_buffer()
+{
+ if (m_fd < 0)
+ return false;
+ u8 buffer[1024];
+ int nread = ::read(m_fd, buffer, sizeof(buffer));
+ if (nread < 0) {
+ set_error(errno);
+ return false;
+ }
+ if (nread == 0) {
+ set_eof(true);
+ return false;
+ }
+ m_buffered_data.append(buffer, nread);
+ return true;
+}
+
+bool CIODevice::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(CIODevice::NotOpen);
+ return true;
+}
+
+bool CIODevice::seek(i64 offset, SeekMode mode, off_t* pos)
+{
+ int m = SEEK_SET;
+ switch (mode) {
+ case SeekMode::SetPosition:
+ m = SEEK_SET;
+ break;
+ case SeekMode::FromCurrentPosition:
+ m = SEEK_CUR;
+ break;
+ case SeekMode::FromEndPosition:
+ m = SEEK_END;
+ break;
+ }
+ off_t rc = lseek(m_fd, offset, m);
+ if (rc < 0) {
+ set_error(errno);
+ if (pos)
+ *pos = -1;
+ return false;
+ }
+ m_buffered_data.clear();
+ m_eof = false;
+ if (pos)
+ *pos = rc;
+ return true;
+}
+
+bool CIODevice::write(const u8* data, int size)
+{
+ int rc = ::write(m_fd, data, size);
+ if (rc < 0) {
+ perror("CIODevice::write: write");
+ set_error(errno);
+ return false;
+ }
+ return rc == size;
+}
+
+int CIODevice::printf(const char* format, ...)
+{
+ va_list ap;
+ va_start(ap, format);
+ // FIXME: We're not propagating write() failures to client here!
+ int ret = printf_internal([this](char*&, char ch) {
+ int rc = write((const u8*)&ch, 1);
+ if (rc < 0)
+ dbgprintf("CIODevice::printf: write: %s\n", strerror(errno));
+ },
+ nullptr, format, ap);
+ va_end(ap);
+ return ret;
+}
diff --git a/Libraries/LibCore/CIODevice.h b/Libraries/LibCore/CIODevice.h
new file mode 100644
index 0000000000..e6de17b46d
--- /dev/null
+++ b/Libraries/LibCore/CIODevice.h
@@ -0,0 +1,74 @@
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/StringView.h>
+#include <LibCore/CObject.h>
+
+class CIODevice : public CObject {
+public:
+ enum OpenMode {
+ NotOpen = 0,
+ ReadOnly = 1,
+ WriteOnly = 2,
+ ReadWrite = 3,
+ Append = 4,
+ Truncate = 8,
+ MustBeNew = 16,
+ };
+
+ virtual ~CIODevice() override;
+
+ int fd() const { return m_fd; }
+ unsigned mode() const { return m_mode; }
+ bool eof() const { return m_eof; }
+
+ int error() const { return m_error; }
+ const char* error_string() const;
+
+ bool has_error() const { return m_error != 0; }
+
+ ByteBuffer read(int max_size);
+ ByteBuffer read_line(int max_size);
+ ByteBuffer read_all();
+
+ bool write(const u8*, int size);
+ bool write(const AK::StringView& v) { return write((const u8*)v.characters(), v.length()); }
+
+ // 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;
+
+ enum class SeekMode {
+ SetPosition,
+ FromCurrentPosition,
+ FromEndPosition,
+ };
+
+ bool seek(i64, SeekMode = SeekMode::SetPosition, off_t* = nullptr);
+
+ virtual bool open(CIODevice::OpenMode) = 0;
+ virtual bool close();
+
+ int printf(const char*, ...);
+
+ virtual const char* class_name() const override { return "CIODevice"; }
+
+protected:
+ explicit CIODevice(CObject* parent = nullptr);
+
+ void set_fd(int fd) { m_fd = fd; }
+ void set_mode(OpenMode mode) { m_mode = mode; }
+ void set_error(int error) { m_error = error; }
+ void set_eof(bool eof) { m_eof = eof; }
+
+private:
+ bool populate_read_buffer();
+ bool can_read_from_fd() const;
+
+ int m_fd { -1 };
+ int m_error { 0 };
+ bool m_eof { false };
+ OpenMode m_mode { NotOpen };
+ Vector<u8> m_buffered_data;
+};
diff --git a/Libraries/LibCore/CLock.h b/Libraries/LibCore/CLock.h
new file mode 100644
index 0000000000..2c31751f92
--- /dev/null
+++ b/Libraries/LibCore/CLock.h
@@ -0,0 +1,125 @@
+#pragma once
+
+#ifdef __serenity__
+
+#include <AK/Assertions.h>
+#include <AK/Types.h>
+#include <unistd.h>
+
+#define memory_barrier() asm volatile("" :: \
+ : "memory")
+
+static inline u32 CAS(volatile u32* mem, u32 newval, u32 oldval)
+{
+ u32 ret;
+ asm volatile(
+ "cmpxchgl %2, %1"
+ : "=a"(ret), "+m"(*mem)
+ : "r"(newval), "0"(oldval)
+ : "cc", "memory");
+ return ret;
+}
+
+class CLock {
+public:
+ CLock() {}
+ ~CLock() {}
+
+ void lock();
+ void unlock();
+
+private:
+ volatile u32 m_lock { 0 };
+ u32 m_level { 0 };
+ int m_holder { -1 };
+};
+
+class CLocker {
+public:
+ [[gnu::always_inline]] inline explicit CLocker(CLock& l)
+ : m_lock(l)
+ {
+ lock();
+ }
+ [[gnu::always_inline]] inline ~CLocker() { unlock(); }
+ [[gnu::always_inline]] inline void unlock() { m_lock.unlock(); }
+ [[gnu::always_inline]] inline void lock() { m_lock.lock(); }
+
+private:
+ CLock& m_lock;
+};
+
+[[gnu::always_inline]] inline void CLock::lock()
+{
+ int tid = gettid();
+ for (;;) {
+ if (CAS(&m_lock, 1, 0) == 0) {
+ if (m_holder == -1 || m_holder == tid) {
+ m_holder = tid;
+ ++m_level;
+ memory_barrier();
+ m_lock = 0;
+ return;
+ }
+ m_lock = 0;
+ }
+ donate(m_holder);
+ }
+}
+
+inline void CLock::unlock()
+{
+ for (;;) {
+ if (CAS(&m_lock, 1, 0) == 0) {
+ ASSERT(m_holder == gettid());
+ ASSERT(m_level);
+ --m_level;
+ if (m_level) {
+ memory_barrier();
+ m_lock = 0;
+ return;
+ }
+ m_holder = -1;
+ memory_barrier();
+ m_lock = 0;
+ return;
+ }
+ donate(m_holder);
+ }
+}
+
+#define LOCKER(lock) CLocker locker(lock)
+
+template<typename T>
+class CLockable {
+public:
+ CLockable() {}
+ CLockable(T&& resource)
+ : m_resource(move(resource))
+ {
+ }
+ CLock& lock() { return m_lock; }
+ T& resource() { return m_resource; }
+
+ T lock_and_copy()
+ {
+ LOCKER(m_lock);
+ return m_resource;
+ }
+
+private:
+ T m_resource;
+ CLock m_lock;
+};
+
+#else
+
+class CLock {
+public:
+ CLock() { }
+ ~CLock() { }
+};
+
+#define LOCKER(x)
+
+#endif
diff --git a/Libraries/LibCore/CNetworkJob.cpp b/Libraries/LibCore/CNetworkJob.cpp
new file mode 100644
index 0000000000..67d02d5d73
--- /dev/null
+++ b/Libraries/LibCore/CNetworkJob.cpp
@@ -0,0 +1,43 @@
+#include <LibCore/CNetworkJob.h>
+#include <LibCore/CNetworkResponse.h>
+#include <stdio.h>
+
+CNetworkJob::CNetworkJob()
+{
+}
+
+CNetworkJob::~CNetworkJob()
+{
+}
+
+void CNetworkJob::did_finish(NonnullRefPtr<CNetworkResponse>&& response)
+{
+ m_response = move(response);
+ printf("%s{%p} job did_finish!\n", class_name(), this);
+ ASSERT(on_finish);
+ on_finish(true);
+ delete_later();
+}
+
+void CNetworkJob::did_fail(Error error)
+{
+ m_error = error;
+ dbgprintf("%s{%p} job did_fail! error: %u (%s)\n", class_name(), this, (unsigned)error, to_string(error));
+ ASSERT(on_finish);
+ on_finish(false);
+ delete_later();
+}
+
+const char* to_string(CNetworkJob::Error error)
+{
+ switch (error) {
+ case CNetworkJob::Error::ProtocolFailed:
+ return "ProtocolFailed";
+ case CNetworkJob::Error::ConnectionFailed:
+ return "ConnectionFailed";
+ case CNetworkJob::Error::TransmissionFailed:
+ return "TransmissionFailed";
+ default:
+ return "(Unknown error)";
+ }
+}
diff --git a/Libraries/LibCore/CNetworkJob.h b/Libraries/LibCore/CNetworkJob.h
new file mode 100644
index 0000000000..b68dba5670
--- /dev/null
+++ b/Libraries/LibCore/CNetworkJob.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include <AK/Function.h>
+#include <LibCore/CObject.h>
+
+class CNetworkResponse;
+
+class CNetworkJob : public CObject {
+public:
+ enum class Error {
+ None,
+ ConnectionFailed,
+ TransmissionFailed,
+ ProtocolFailed,
+ };
+ virtual ~CNetworkJob() override;
+
+ Function<void(bool success)> on_finish;
+
+ bool has_error() const { return m_error != Error::None; }
+ Error error() const { return m_error; }
+ CNetworkResponse* response() { return m_response.ptr(); }
+ const CNetworkResponse* response() const { return m_response.ptr(); }
+
+ virtual void start() = 0;
+
+ virtual const char* class_name() const override { return "CNetworkJob"; }
+
+protected:
+ CNetworkJob();
+ void did_finish(NonnullRefPtr<CNetworkResponse>&&);
+ void did_fail(Error);
+
+private:
+ RefPtr<CNetworkResponse> m_response;
+ Error m_error { Error::None };
+};
+
+const char* to_string(CNetworkJob::Error);
diff --git a/Libraries/LibCore/CNetworkResponse.cpp b/Libraries/LibCore/CNetworkResponse.cpp
new file mode 100644
index 0000000000..1550d8ac69
--- /dev/null
+++ b/Libraries/LibCore/CNetworkResponse.cpp
@@ -0,0 +1,10 @@
+#include <LibCore/CNetworkResponse.h>
+
+CNetworkResponse::CNetworkResponse(ByteBuffer&& payload)
+ : m_payload(payload)
+{
+}
+
+CNetworkResponse::~CNetworkResponse()
+{
+}
diff --git a/Libraries/LibCore/CNetworkResponse.h b/Libraries/LibCore/CNetworkResponse.h
new file mode 100644
index 0000000000..c637f6a514
--- /dev/null
+++ b/Libraries/LibCore/CNetworkResponse.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <AK/ByteBuffer.h>
+#include <AK/RefCounted.h>
+
+class CNetworkResponse : public RefCounted<CNetworkResponse> {
+public:
+ virtual ~CNetworkResponse();
+
+ bool is_error() const { return m_error; }
+ const ByteBuffer& payload() const { return m_payload; }
+
+protected:
+ explicit CNetworkResponse(ByteBuffer&&);
+
+ bool m_error { false };
+ ByteBuffer m_payload;
+};
diff --git a/Libraries/LibCore/CNotifier.cpp b/Libraries/LibCore/CNotifier.cpp
new file mode 100644
index 0000000000..c124191ac1
--- /dev/null
+++ b/Libraries/LibCore/CNotifier.cpp
@@ -0,0 +1,15 @@
+#include <LibCore/CEvent.h>
+#include <LibCore/CEventLoop.h>
+#include <LibCore/CNotifier.h>
+
+CNotifier::CNotifier(int fd, unsigned event_mask)
+ : m_fd(fd)
+ , m_event_mask(event_mask)
+{
+ CEventLoop::register_notifier({}, *this);
+}
+
+CNotifier::~CNotifier()
+{
+ CEventLoop::unregister_notifier({}, *this);
+}
diff --git a/Libraries/LibCore/CNotifier.h b/Libraries/LibCore/CNotifier.h
new file mode 100644
index 0000000000..196cad8388
--- /dev/null
+++ b/Libraries/LibCore/CNotifier.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <AK/Function.h>
+
+class CNotifier {
+public:
+ enum Event {
+ None = 0,
+ Read = 1,
+ Write = 2,
+ Exceptional = 4,
+ };
+ CNotifier(int fd, unsigned event_mask);
+ ~CNotifier();
+
+ Function<void()> on_ready_to_read;
+ Function<void()> on_ready_to_write;
+
+ int fd() const { return m_fd; }
+ unsigned event_mask() const { return m_event_mask; }
+ void set_event_mask(unsigned event_mask) { m_event_mask = event_mask; }
+
+private:
+ int m_fd { -1 };
+ unsigned m_event_mask { 0 };
+};
diff --git a/Libraries/LibCore/CObject.cpp b/Libraries/LibCore/CObject.cpp
new file mode 100644
index 0000000000..bbee4501dd
--- /dev/null
+++ b/Libraries/LibCore/CObject.cpp
@@ -0,0 +1,113 @@
+#include <AK/Assertions.h>
+#include <AK/kstdio.h>
+#include <LibCore/CEvent.h>
+#include <LibCore/CEventLoop.h>
+#include <LibCore/CObject.h>
+#include <stdio.h>
+
+CObject::CObject(CObject* parent, bool is_widget)
+ : m_parent(parent)
+ , m_widget(is_widget)
+{
+ if (m_parent)
+ m_parent->add_child(*this);
+}
+
+CObject::~CObject()
+{
+ stop_timer();
+ if (m_parent)
+ m_parent->remove_child(*this);
+ auto children_to_delete = move(m_children);
+ for (auto* child : children_to_delete)
+ delete child;
+}
+
+void CObject::event(CEvent& event)
+{
+ switch (event.type()) {
+ case CEvent::Timer:
+ return timer_event(static_cast<CTimerEvent&>(event));
+ case CEvent::DeferredDestroy:
+ delete this;
+ break;
+ case CEvent::ChildAdded:
+ case CEvent::ChildRemoved:
+ return child_event(static_cast<CChildEvent&>(event));
+ case CEvent::Invalid:
+ ASSERT_NOT_REACHED();
+ break;
+ default:
+ break;
+ }
+}
+
+void CObject::add_child(CObject& object)
+{
+ // FIXME: Should we support reparenting objects?
+ ASSERT(!object.parent() || object.parent() == this);
+ object.m_parent = this;
+ m_children.append(&object);
+ event(*make<CChildEvent>(CEvent::ChildAdded, object));
+}
+
+void CObject::remove_child(CObject& object)
+{
+ for (ssize_t i = 0; i < m_children.size(); ++i) {
+ if (m_children[i] == &object) {
+ m_children.remove(i);
+ event(*make<CChildEvent>(CEvent::ChildRemoved, object));
+ return;
+ }
+ }
+}
+
+void CObject::timer_event(CTimerEvent&)
+{
+}
+
+void CObject::child_event(CChildEvent&)
+{
+}
+
+void CObject::start_timer(int ms)
+{
+ if (m_timer_id) {
+ dbgprintf("CObject{%p} already has a timer!\n", this);
+ ASSERT_NOT_REACHED();
+ }
+
+ m_timer_id = CEventLoop::register_timer(*this, ms, true);
+}
+
+void CObject::stop_timer()
+{
+ if (!m_timer_id)
+ return;
+ bool success = CEventLoop::unregister_timer(m_timer_id);
+ ASSERT(success);
+ m_timer_id = 0;
+}
+
+void CObject::delete_later()
+{
+ CEventLoop::current().post_event(*this, make<CEvent>(CEvent::DeferredDestroy));
+}
+
+void CObject::dump_tree(int indent)
+{
+ for (int i = 0; i < indent; ++i) {
+ printf(" ");
+ }
+ printf("%s{%p}\n", class_name(), this);
+
+ for_each_child([&](auto& child) {
+ child.dump_tree(indent + 2);
+ return IterationDecision::Continue;
+ });
+}
+
+void CObject::deferred_invoke(Function<void(CObject&)> invokee)
+{
+ CEventLoop::current().post_event(*this, make<CDeferredInvocationEvent>(move(invokee)));
+}
diff --git a/Libraries/LibCore/CObject.h b/Libraries/LibCore/CObject.h
new file mode 100644
index 0000000000..392a94e189
--- /dev/null
+++ b/Libraries/LibCore/CObject.h
@@ -0,0 +1,94 @@
+#pragma once
+
+#include <AK/Function.h>
+#include <AK/StdLibExtras.h>
+#include <AK/Vector.h>
+#include <AK/Weakable.h>
+
+class CEvent;
+class CChildEvent;
+class CTimerEvent;
+
+class CObject : public Weakable<CObject> {
+public:
+ CObject(CObject* parent = nullptr, bool is_widget = false);
+ virtual ~CObject();
+
+ virtual const char* class_name() const { return "CObject"; }
+
+ virtual void event(CEvent&);
+
+ Vector<CObject*>& children() { return m_children; }
+ const Vector<CObject*>& children() const { return m_children; }
+
+ template<typename Callback>
+ void for_each_child(Callback callback)
+ {
+ for (auto* child : m_children) {
+ if (callback(*child) == IterationDecision::Break)
+ return;
+ }
+ }
+
+ template<typename T, typename Callback>
+ void for_each_child_of_type(Callback callback);
+
+ CObject* parent() { return m_parent; }
+ const CObject* parent() const { return m_parent; }
+
+ void start_timer(int ms);
+ void stop_timer();
+ bool has_timer() const { return m_timer_id; }
+
+ void add_child(CObject&);
+ void remove_child(CObject&);
+
+ void delete_later();
+
+ void dump_tree(int indent = 0);
+
+ void deferred_invoke(Function<void(CObject&)>);
+
+ bool is_widget() const { return m_widget; }
+ virtual bool is_window() const { return false; }
+
+protected:
+ virtual void timer_event(CTimerEvent&);
+ virtual void child_event(CChildEvent&);
+
+private:
+ CObject* m_parent { nullptr };
+ int m_timer_id { 0 };
+ bool m_widget { false };
+ Vector<CObject*> m_children;
+};
+
+template<typename T>
+inline bool is(const CObject&) { return false; }
+
+template<>
+inline bool is<CObject>(const CObject&) { return true; }
+
+template<typename T>
+inline T& to(CObject& object)
+{
+ ASSERT(is<typename RemoveConst<T>::Type>(object));
+ return static_cast<T&>(object);
+}
+
+template<typename T>
+inline const T& to(const CObject& object)
+{
+ ASSERT(is<typename RemoveConst<T>::Type>(object));
+ return static_cast<const T&>(object);
+}
+
+template<typename T, typename Callback>
+inline void CObject::for_each_child_of_type(Callback callback)
+{
+ for_each_child([&](auto& child) {
+ if (is<T>(child))
+ return callback(to<T>(child));
+ return IterationDecision::Continue;
+ });
+}
diff --git a/Libraries/LibCore/CProcessStatisticsReader.cpp b/Libraries/LibCore/CProcessStatisticsReader.cpp
new file mode 100644
index 0000000000..7f0456d53e
--- /dev/null
+++ b/Libraries/LibCore/CProcessStatisticsReader.cpp
@@ -0,0 +1,58 @@
+#include <AK/JsonArray.h>
+#include <AK/JsonObject.h>
+#include <AK/JsonValue.h>
+#include <LibCore/CFile.h>
+#include <LibCore/CProcessStatisticsReader.h>
+#include <pwd.h>
+#include <stdio.h>
+
+CProcessStatisticsReader::CProcessStatisticsReader()
+{
+ setpwent();
+ while (auto* passwd = getpwent())
+ m_usernames.set(passwd->pw_uid, passwd->pw_name);
+ endpwent();
+}
+
+HashMap<pid_t, CProcessStatistics> CProcessStatisticsReader::get_map()
+{
+ HashMap<pid_t, CProcessStatistics> res;
+ update_map(res);
+ return res;
+}
+
+void CProcessStatisticsReader::update_map(HashMap<pid_t, CProcessStatistics>& map)
+{
+ CFile file("/proc/all");
+ if (!file.open(CIODevice::ReadOnly)) {
+ fprintf(stderr, "CProcessHelper : failed to open /proc/all: %s\n", file.error_string());
+ return;
+ }
+
+ auto file_contents = file.read_all();
+ auto json = JsonValue::from_string({ file_contents.data(), file_contents.size() });
+ json.as_array().for_each([&](auto& value) {
+ const JsonObject& process_object = value.as_object();
+ CProcessStatistics process;
+ process.pid = process_object.get("pid").to_u32();
+ process.nsched = process_object.get("times_scheduled").to_u32();
+ process.uid = process_object.get("uid").to_u32();
+ process.username = get_username_from_uid(process.uid);
+ process.priority = process_object.get("priority").to_string();
+ process.syscalls = process_object.get("syscall_count").to_u32();
+ process.state = process_object.get("state").to_string();
+ process.name = process_object.get("name").to_string();
+ process.virtual_size = process_object.get("amount_virtual").to_u32();
+ process.physical_size = process_object.get("amount_resident").to_u32();
+ map.set(process.pid, process);
+ });
+}
+
+String CProcessStatisticsReader::get_username_from_uid(const uid_t uid)
+{
+ auto it = m_usernames.find(uid);
+ if (it != m_usernames.end())
+ return (*it).value;
+ else
+ return String::number(uid);
+}
diff --git a/Libraries/LibCore/CProcessStatisticsReader.h b/Libraries/LibCore/CProcessStatisticsReader.h
new file mode 100644
index 0000000000..adb96f39ad
--- /dev/null
+++ b/Libraries/LibCore/CProcessStatisticsReader.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <AK/AKString.h>
+#include <AK/HashMap.h>
+
+struct CProcessStatistics {
+ pid_t pid;
+ unsigned nsched;
+ String name;
+ String state;
+ String username;
+ uid_t uid;
+ String priority;
+ size_t virtual_size;
+ size_t physical_size;
+ unsigned syscalls;
+};
+
+class CProcessStatisticsReader {
+public:
+ CProcessStatisticsReader();
+ HashMap<pid_t, CProcessStatistics> get_map();
+
+private:
+ void update_map(HashMap<pid_t, CProcessStatistics>& map);
+ String get_username_from_uid(const uid_t uid);
+
+ HashMap<uid_t, String> m_usernames;
+};
diff --git a/Libraries/LibCore/CSocket.cpp b/Libraries/LibCore/CSocket.cpp
new file mode 100644
index 0000000000..4479969c40
--- /dev/null
+++ b/Libraries/LibCore/CSocket.cpp
@@ -0,0 +1,93 @@
+#include <LibCore/CNotifier.h>
+#include <LibCore/CSocket.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+
+CSocket::CSocket(Type type, CObject* parent)
+ : CIODevice(parent)
+ , m_type(type)
+{
+}
+
+CSocket::~CSocket()
+{
+}
+
+bool CSocket::connect(const String& hostname, int port)
+{
+ auto* hostent = gethostbyname(hostname.characters());
+ if (!hostent) {
+ dbgprintf("CSocket::connect: Unable to resolve '%s'\n", hostname.characters());
+ return false;
+ }
+
+ IPv4Address host_address((const u8*)hostent->h_addr_list[0]);
+ dbgprintf("CSocket::connect: Resolved '%s' to %s\n", hostname.characters(), host_address.to_string().characters());
+ return connect(host_address, port);
+}
+
+bool CSocket::connect(const CSocketAddress& address, int port)
+{
+ ASSERT(!is_connected());
+ ASSERT(address.type() == CSocketAddress::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);
+
+ m_destination_address = address;
+ m_destination_port = port;
+
+ dbgprintf("Connecting to %s...", address.to_string().characters());
+ fflush(stdout);
+ int rc = ::connect(fd(), (struct sockaddr*)&addr, sizeof(addr));
+ if (rc < 0) {
+ if (errno == EINPROGRESS) {
+ dbgprintf("in progress.\n");
+ m_notifier = make<CNotifier>(fd(), CNotifier::Event::Write);
+ m_notifier->on_ready_to_write = [this] {
+ dbgprintf("%s{%p} connected!\n", class_name(), this);
+ m_connected = true;
+ m_notifier->set_event_mask(CNotifier::Event::None);
+ if (on_connected)
+ on_connected();
+ };
+ return true;
+ }
+ perror("connect");
+ exit(1);
+ }
+ dbgprintf("ok!\n");
+ m_connected = true;
+ return true;
+}
+
+ByteBuffer CSocket::receive(int max_size)
+{
+ auto buffer = read(max_size);
+ if (eof()) {
+ dbgprintf("CSocket{%p}: Connection appears to have closed in receive().\n", this);
+ m_connected = false;
+ }
+ return buffer;
+}
+
+bool CSocket::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/Libraries/LibCore/CSocket.h b/Libraries/LibCore/CSocket.h
new file mode 100644
index 0000000000..587f80f290
--- /dev/null
+++ b/Libraries/LibCore/CSocket.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include <LibCore/CIODevice.h>
+#include <LibCore/CSocketAddress.h>
+
+class CNotifier;
+
+class CSocket : public CIODevice {
+public:
+ enum class Type {
+ Invalid,
+ TCP,
+ UDP
+ };
+ virtual ~CSocket() override;
+
+ bool connect(const String& hostname, int port);
+ bool connect(const CSocketAddress&, int port);
+
+ ByteBuffer receive(int max_size);
+ bool send(const ByteBuffer&);
+
+ bool is_connected() const { return m_connected; }
+
+ CSocketAddress source_address() const { return m_source_address; }
+ int source_port() const { return m_source_port; }
+
+ CSocketAddress destination_address() const { return m_source_address; }
+ int destination_port() const { return m_destination_port; }
+
+ Function<void()> on_connected;
+
+ virtual const char* class_name() const override { return "CSocket"; }
+
+protected:
+ CSocket(Type, CObject* parent);
+
+ CSocketAddress m_source_address;
+ CSocketAddress m_destination_address;
+ int m_source_port { -1 };
+ int m_destination_port { -1 };
+ bool m_connected { false };
+
+private:
+ virtual bool open(CIODevice::OpenMode) override { ASSERT_NOT_REACHED(); }
+ Type m_type { Type::Invalid };
+ OwnPtr<CNotifier> m_notifier;
+};
diff --git a/Libraries/LibCore/CSocketAddress.h b/Libraries/LibCore/CSocketAddress.h
new file mode 100644
index 0000000000..019a0869fb
--- /dev/null
+++ b/Libraries/LibCore/CSocketAddress.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include <AK/IPv4Address.h>
+
+class CSocketAddress {
+public:
+ enum class Type {
+ Invalid,
+ IPv4,
+ Local
+ };
+
+ CSocketAddress() {}
+ CSocketAddress(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 "[CSocketAddress]";
+ }
+ }
+
+private:
+ Type m_type { Type::Invalid };
+ IPv4Address m_ipv4_address;
+};
diff --git a/Libraries/LibCore/CTCPSocket.cpp b/Libraries/LibCore/CTCPSocket.cpp
new file mode 100644
index 0000000000..5f3702a482
--- /dev/null
+++ b/Libraries/LibCore/CTCPSocket.cpp
@@ -0,0 +1,19 @@
+#include <LibCore/CTCPSocket.h>
+#include <sys/socket.h>
+
+CTCPSocket::CTCPSocket(CObject* parent)
+ : CSocket(CSocket::Type::TCP, parent)
+{
+ int fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
+ if (fd < 0) {
+ set_error(fd);
+ } else {
+ set_fd(fd);
+ set_mode(CIODevice::ReadWrite);
+ set_error(0);
+ }
+}
+
+CTCPSocket::~CTCPSocket()
+{
+}
diff --git a/Libraries/LibCore/CTCPSocket.h b/Libraries/LibCore/CTCPSocket.h
new file mode 100644
index 0000000000..fe4f3ffb3a
--- /dev/null
+++ b/Libraries/LibCore/CTCPSocket.h
@@ -0,0 +1,9 @@
+#include <LibCore/CSocket.h>
+
+class CTCPSocket final : public CSocket {
+public:
+ explicit CTCPSocket(CObject* parent = nullptr);
+ virtual ~CTCPSocket() override;
+
+private:
+};
diff --git a/Libraries/LibCore/CTimer.cpp b/Libraries/LibCore/CTimer.cpp
new file mode 100644
index 0000000000..fb55ad7e63
--- /dev/null
+++ b/Libraries/LibCore/CTimer.cpp
@@ -0,0 +1,61 @@
+#include <LibCore/CTimer.h>
+
+CTimer::CTimer(CObject* parent)
+ : CObject(parent)
+{
+}
+
+CTimer::CTimer(int interval, Function<void()>&& timeout_handler, CObject* parent)
+ : CObject(parent)
+ , on_timeout(move(timeout_handler))
+{
+ start(interval);
+}
+
+CTimer::~CTimer()
+{
+}
+
+void CTimer::start()
+{
+ start(m_interval);
+}
+
+void CTimer::start(int interval)
+{
+ if (m_active)
+ return;
+ m_interval = interval;
+ start_timer(interval);
+ m_active = true;
+}
+
+void CTimer::restart(int interval)
+{
+ if (m_active)
+ stop();
+ start(interval);
+}
+
+void CTimer::stop()
+{
+ if (!m_active)
+ return;
+ stop_timer();
+ m_active = false;
+}
+
+void CTimer::timer_event(CTimerEvent&)
+{
+ if (m_single_shot)
+ stop();
+ else {
+ if (m_interval_dirty) {
+ stop();
+ start(m_interval);
+ }
+ }
+
+ if (on_timeout)
+ on_timeout();
+}
diff --git a/Libraries/LibCore/CTimer.h b/Libraries/LibCore/CTimer.h
new file mode 100644
index 0000000000..b6c1c2005f
--- /dev/null
+++ b/Libraries/LibCore/CTimer.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include <AK/Function.h>
+#include <LibCore/CObject.h>
+
+class CTimer final : public CObject {
+public:
+ explicit CTimer(CObject* parent = nullptr);
+ CTimer(int interval, Function<void()>&& timeout_handler, CObject* parent = nullptr);
+ virtual ~CTimer() override;
+
+ void start();
+ void start(int interval);
+ void restart(int interval);
+ void stop();
+
+ bool is_active() const { return m_active; }
+ int interval() const { return m_interval; }
+ void set_interval(int interval)
+ {
+ if (m_interval == interval)
+ return;
+ m_interval = interval;
+ m_interval_dirty = true;
+ }
+
+ bool is_single_shot() const { return m_single_shot; }
+ void set_single_shot(bool single_shot) { m_single_shot = single_shot; }
+
+ Function<void()> on_timeout;
+
+ virtual const char* class_name() const override { return "CTimer"; }
+
+private:
+ virtual void timer_event(CTimerEvent&) override;
+
+ bool m_active { false };
+ bool m_single_shot { false };
+ bool m_interval_dirty { false };
+ int m_interval { 0 };
+};
diff --git a/Libraries/LibCore/CUserInfo.cpp b/Libraries/LibCore/CUserInfo.cpp
new file mode 100644
index 0000000000..d5308d454e
--- /dev/null
+++ b/Libraries/LibCore/CUserInfo.cpp
@@ -0,0 +1,17 @@
+#include "CUserInfo.h"
+#include <pwd.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+const char* get_current_user_home_path()
+{
+ if (auto* home_env = getenv("HOME"))
+ return home_env;
+
+ auto d = "/";
+ uid_t uid = getuid();
+ if (auto* pwd = getpwuid(uid))
+ return pwd->pw_dir;
+
+ return d;
+}
diff --git a/Libraries/LibCore/CUserInfo.h b/Libraries/LibCore/CUserInfo.h
new file mode 100644
index 0000000000..b47cec4af1
--- /dev/null
+++ b/Libraries/LibCore/CUserInfo.h
@@ -0,0 +1 @@
+const char* get_current_user_home_path();
diff --git a/Libraries/LibCore/Makefile b/Libraries/LibCore/Makefile
new file mode 100644
index 0000000000..1f88103724
--- /dev/null
+++ b/Libraries/LibCore/Makefile
@@ -0,0 +1,49 @@
+include ../../Makefile.common
+
+OBJS = \
+ CArgsParser.o \
+ CIODevice.o \
+ CFile.o \
+ CSocket.o \
+ CTCPSocket.o \
+ CElapsedTimer.o \
+ CNotifier.o \
+ CHttpRequest.o \
+ CHttpResponse.o \
+ CHttpJob.o \
+ CNetworkJob.o \
+ CNetworkResponse.o \
+ CObject.o \
+ CTimer.o \
+ CEventLoop.o \
+ CConfigFile.o \
+ CEvent.o \
+ CProcessStatisticsReader.o \
+ CDirIterator.o \
+ CUserInfo.o
+
+LIBRARY = libcore.a
+DEFINES += -DUSERLAND
+
+all: $(LIBRARY)
+
+$(LIBRARY): $(OBJS)
+ @echo "LIB $@"; $(AR) rcs $@ $(OBJS) $(LIBS)
+
+.cpp.o:
+ @echo "CXX $<"; $(CXX) $(CXXFLAGS) -o $@ -c $<
+
+-include $(OBJS:%.o=%.d)
+
+clean:
+ @echo "CLEAN"; rm -f $(LIBRARY) $(OBJS) *.d
+
+install: $(LIBRARY)
+ mkdir -p ../Root/usr/include/LibCore
+ mkdir -p ../Root/usr/include/AK
+ mkdir -p ../Root/usr/lib
+ # Copy headers
+ rsync -r -a --include '*/' --include '*.h' --exclude '*' . ../Root/usr/include/LibCore
+ rsync -r -a --include '*/' --include '*.h' --exclude '*' ../AK/ ../Root/usr/include/AK
+ # Install the library
+ cp $(LIBRARY) ../Root/usr/lib
diff --git a/Libraries/LibCore/install.sh b/Libraries/LibCore/install.sh
new file mode 100755
index 0000000000..fedc4b73fb
--- /dev/null
+++ b/Libraries/LibCore/install.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+mkdir -p ../Root/usr/include/LibCore/
+cp *.h ../Root/usr/include/LibCore/
+cp libcore.a ../Root/usr/lib/