mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 18:57:35 +00:00
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/.
This commit is contained in:
parent
63814ffebf
commit
04b9dc2d30
328 changed files with 36 additions and 36 deletions
237
Libraries/LibCore/CArgsParser.cpp
Normal file
237
Libraries/LibCore/CArgsParser.cpp
Normal file
|
@ -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());
|
||||
}
|
69
Libraries/LibCore/CArgsParser.h
Normal file
69
Libraries/LibCore/CArgsParser.h
Normal file
|
@ -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;
|
||||
};
|
233
Libraries/LibCore/CConfigFile.cpp
Normal file
233
Libraries/LibCore/CConfigFile.cpp
Normal file
|
@ -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;
|
||||
}
|
51
Libraries/LibCore/CConfigFile.h
Normal file
51
Libraries/LibCore/CConfigFile.h
Normal file
|
@ -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 };
|
||||
};
|
67
Libraries/LibCore/CDirIterator.cpp
Normal file
67
Libraries/LibCore/CDirIterator.cpp
Normal file
|
@ -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;
|
||||
}
|
29
Libraries/LibCore/CDirIterator.h
Normal file
29
Libraries/LibCore/CDirIterator.h
Normal file
|
@ -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();
|
||||
};
|
20
Libraries/LibCore/CElapsedTimer.cpp
Normal file
20
Libraries/LibCore/CElapsedTimer.cpp
Normal file
|
@ -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;
|
||||
}
|
18
Libraries/LibCore/CElapsedTimer.h
Normal file
18
Libraries/LibCore/CElapsedTimer.h
Normal file
|
@ -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
|
||||
};
|
||||
};
|
12
Libraries/LibCore/CEvent.cpp
Normal file
12
Libraries/LibCore/CEvent.cpp
Normal file
|
@ -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()
|
||||
{
|
||||
}
|
74
Libraries/LibCore/CEvent.h
Normal file
74
Libraries/LibCore/CEvent.h
Normal file
|
@ -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;
|
||||
};
|
302
Libraries/LibCore/CEventLoop.cpp
Normal file
302
Libraries/LibCore/CEventLoop.cpp
Normal file
|
@ -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(¬ifier);
|
||||
}
|
||||
|
||||
void CEventLoop::unregister_notifier(Badge<CNotifier>, CNotifier& notifier)
|
||||
{
|
||||
s_notifiers->remove(¬ifier);
|
||||
}
|
89
Libraries/LibCore/CEventLoop.h
Normal file
89
Libraries/LibCore/CEventLoop.h
Normal file
|
@ -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;
|
||||
};
|
51
Libraries/LibCore/CFile.cpp
Normal file
51
Libraries/LibCore/CFile.cpp
Normal file
|
@ -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;
|
||||
}
|
28
Libraries/LibCore/CFile.h
Normal file
28
Libraries/LibCore/CFile.h
Normal file
|
@ -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 };
|
||||
};
|
118
Libraries/LibCore/CHttpJob.cpp
Normal file
118
Libraries/LibCore/CHttpJob.cpp
Normal file
|
@ -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);
|
||||
}
|
33
Libraries/LibCore/CHttpJob.h
Normal file
33
Libraries/LibCore/CHttpJob.h
Normal file
|
@ -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;
|
||||
};
|
44
Libraries/LibCore/CHttpRequest.cpp
Normal file
44
Libraries/LibCore/CHttpRequest.cpp
Normal file
|
@ -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();
|
||||
}
|
39
Libraries/LibCore/CHttpRequest.h
Normal file
39
Libraries/LibCore/CHttpRequest.h
Normal file
|
@ -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 };
|
||||
};
|
12
Libraries/LibCore/CHttpResponse.cpp
Normal file
12
Libraries/LibCore/CHttpResponse.cpp
Normal file
|
@ -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()
|
||||
{
|
||||
}
|
23
Libraries/LibCore/CHttpResponse.h
Normal file
23
Libraries/LibCore/CHttpResponse.h
Normal file
|
@ -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;
|
||||
};
|
242
Libraries/LibCore/CIODevice.cpp
Normal file
242
Libraries/LibCore/CIODevice.cpp
Normal file
|
@ -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;
|
||||
}
|
74
Libraries/LibCore/CIODevice.h
Normal file
74
Libraries/LibCore/CIODevice.h
Normal file
|
@ -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;
|
||||
};
|
125
Libraries/LibCore/CLock.h
Normal file
125
Libraries/LibCore/CLock.h
Normal file
|
@ -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
|
43
Libraries/LibCore/CNetworkJob.cpp
Normal file
43
Libraries/LibCore/CNetworkJob.cpp
Normal file
|
@ -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)";
|
||||
}
|
||||
}
|
39
Libraries/LibCore/CNetworkJob.h
Normal file
39
Libraries/LibCore/CNetworkJob.h
Normal file
|
@ -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);
|
10
Libraries/LibCore/CNetworkResponse.cpp
Normal file
10
Libraries/LibCore/CNetworkResponse.cpp
Normal file
|
@ -0,0 +1,10 @@
|
|||
#include <LibCore/CNetworkResponse.h>
|
||||
|
||||
CNetworkResponse::CNetworkResponse(ByteBuffer&& payload)
|
||||
: m_payload(payload)
|
||||
{
|
||||
}
|
||||
|
||||
CNetworkResponse::~CNetworkResponse()
|
||||
{
|
||||
}
|
18
Libraries/LibCore/CNetworkResponse.h
Normal file
18
Libraries/LibCore/CNetworkResponse.h
Normal file
|
@ -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;
|
||||
};
|
15
Libraries/LibCore/CNotifier.cpp
Normal file
15
Libraries/LibCore/CNotifier.cpp
Normal file
|
@ -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);
|
||||
}
|
26
Libraries/LibCore/CNotifier.h
Normal file
26
Libraries/LibCore/CNotifier.h
Normal file
|
@ -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 };
|
||||
};
|
113
Libraries/LibCore/CObject.cpp
Normal file
113
Libraries/LibCore/CObject.cpp
Normal file
|
@ -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)));
|
||||
}
|
94
Libraries/LibCore/CObject.h
Normal file
94
Libraries/LibCore/CObject.h
Normal file
|
@ -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;
|
||||
});
|
||||
}
|
58
Libraries/LibCore/CProcessStatisticsReader.cpp
Normal file
58
Libraries/LibCore/CProcessStatisticsReader.cpp
Normal file
|
@ -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);
|
||||
}
|
29
Libraries/LibCore/CProcessStatisticsReader.h
Normal file
29
Libraries/LibCore/CProcessStatisticsReader.h
Normal file
|
@ -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;
|
||||
};
|
93
Libraries/LibCore/CSocket.cpp
Normal file
93
Libraries/LibCore/CSocket.cpp
Normal file
|
@ -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;
|
||||
}
|
48
Libraries/LibCore/CSocket.h
Normal file
48
Libraries/LibCore/CSocket.h
Normal file
|
@ -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;
|
||||
};
|
37
Libraries/LibCore/CSocketAddress.h
Normal file
37
Libraries/LibCore/CSocketAddress.h
Normal file
|
@ -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;
|
||||
};
|
19
Libraries/LibCore/CTCPSocket.cpp
Normal file
19
Libraries/LibCore/CTCPSocket.cpp
Normal file
|
@ -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()
|
||||
{
|
||||
}
|
9
Libraries/LibCore/CTCPSocket.h
Normal file
9
Libraries/LibCore/CTCPSocket.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
#include <LibCore/CSocket.h>
|
||||
|
||||
class CTCPSocket final : public CSocket {
|
||||
public:
|
||||
explicit CTCPSocket(CObject* parent = nullptr);
|
||||
virtual ~CTCPSocket() override;
|
||||
|
||||
private:
|
||||
};
|
61
Libraries/LibCore/CTimer.cpp
Normal file
61
Libraries/LibCore/CTimer.cpp
Normal file
|
@ -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();
|
||||
}
|
41
Libraries/LibCore/CTimer.h
Normal file
41
Libraries/LibCore/CTimer.h
Normal file
|
@ -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 };
|
||||
};
|
17
Libraries/LibCore/CUserInfo.cpp
Normal file
17
Libraries/LibCore/CUserInfo.cpp
Normal file
|
@ -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;
|
||||
}
|
1
Libraries/LibCore/CUserInfo.h
Normal file
1
Libraries/LibCore/CUserInfo.h
Normal file
|
@ -0,0 +1 @@
|
|||
const char* get_current_user_home_path();
|
49
Libraries/LibCore/Makefile
Normal file
49
Libraries/LibCore/Makefile
Normal file
|
@ -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
|
5
Libraries/LibCore/install.sh
Executable file
5
Libraries/LibCore/install.sh
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
|
||||
mkdir -p ../Root/usr/include/LibCore/
|
||||
cp *.h ../Root/usr/include/LibCore/
|
||||
cp libcore.a ../Root/usr/lib/
|
Loading…
Add table
Add a link
Reference in a new issue