From 0ab40dc1ab4e689921da54080b135e1d22b1c586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 14 Mar 2017 21:45:23 +0100 Subject: Refactoring louloulibs and cmake Use OBJECT libraries Remove the louloulibs directory Write FOUND variables in the cache --- src/utils/encoding.cpp | 254 +++++++++++++++++++++++++++++++++++++ src/utils/encoding.hpp | 43 +++++++ src/utils/get_first_non_empty.cpp | 11 ++ src/utils/get_first_non_empty.hpp | 20 +++ src/utils/revstr.cpp | 9 ++ src/utils/revstr.hpp | 11 ++ src/utils/scopeguard.hpp | 98 ++++++++++++++ src/utils/sha1.cpp | 32 +++++ src/utils/sha1.hpp | 5 + src/utils/split.cpp | 19 +++ src/utils/split.hpp | 12 ++ src/utils/string.cpp | 28 ++++ src/utils/string.hpp | 10 ++ src/utils/system.cpp | 21 +++ src/utils/system.hpp | 8 ++ src/utils/time.cpp | 70 ++++++++++ src/utils/time.hpp | 10 ++ src/utils/timed_events.cpp | 47 +++++++ src/utils/timed_events.hpp | 132 +++++++++++++++++++ src/utils/timed_events_manager.cpp | 73 +++++++++++ src/utils/tolower.cpp | 13 ++ src/utils/tolower.hpp | 11 ++ src/utils/xdg.cpp | 29 +++++ src/utils/xdg.hpp | 14 ++ 24 files changed, 980 insertions(+) create mode 100644 src/utils/encoding.cpp create mode 100644 src/utils/encoding.hpp create mode 100644 src/utils/get_first_non_empty.cpp create mode 100644 src/utils/get_first_non_empty.hpp create mode 100644 src/utils/revstr.cpp create mode 100644 src/utils/revstr.hpp create mode 100644 src/utils/scopeguard.hpp create mode 100644 src/utils/sha1.cpp create mode 100644 src/utils/sha1.hpp create mode 100644 src/utils/split.cpp create mode 100644 src/utils/split.hpp create mode 100644 src/utils/string.cpp create mode 100644 src/utils/string.hpp create mode 100644 src/utils/system.cpp create mode 100644 src/utils/system.hpp create mode 100644 src/utils/time.cpp create mode 100644 src/utils/time.hpp create mode 100644 src/utils/timed_events.cpp create mode 100644 src/utils/timed_events.hpp create mode 100644 src/utils/timed_events_manager.cpp create mode 100644 src/utils/tolower.cpp create mode 100644 src/utils/tolower.hpp create mode 100644 src/utils/xdg.cpp create mode 100644 src/utils/xdg.hpp (limited to 'src/utils') diff --git a/src/utils/encoding.cpp b/src/utils/encoding.cpp new file mode 100644 index 0000000..aa91dac --- /dev/null +++ b/src/utils/encoding.cpp @@ -0,0 +1,254 @@ +#include + +#include + +#include + +#include +#include +#include +#include + +#include +#include + +/** + * The UTF-8-encoded character used as a place holder when a character conversion fails. + * This is U+FFFD � "replacement character" + */ +static const char* invalid_char = "\xef\xbf\xbd"; +static const size_t invalid_char_len = 3; + +namespace utils +{ + /** + * Based on http://en.wikipedia.org/wiki/UTF-8#Description + */ + std::size_t get_next_codepoint_size(const unsigned char c) + { + if ((c & 0b11111000) == 0b11110000) // 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + return 4; + else if ((c & 0b11110000) == 0b11100000) // 3 bytes: 1110xxx 10xxxxxx 10xxxxxx + return 3; + else if ((c & 0b11100000) == 0b11000000) // 2 bytes: 110xxxxx 10xxxxxx + return 2; + return 1; // 1 byte: 0xxxxxxx + } + + bool is_valid_utf8(const char* s) + { + if (!s) + return false; + + const unsigned char* str = reinterpret_cast(s); + + while (*str) + { + const auto codepoint_size = get_next_codepoint_size(str[0]); + if (codepoint_size == 4) + { + if (!str[1] || !str[2] || !str[3] + || ((str[1] & 0b11000000) != 0b10000000) + || ((str[2] & 0b11000000) != 0b10000000) + || ((str[3] & 0b11000000) != 0b10000000)) + return false; + } + else if (codepoint_size == 3) + { + if (!str[1] || !str[2] + || ((str[1] & 0b11000000) != 0b10000000) + || ((str[2] & 0b11000000) != 0b10000000)) + return false; + } + else if (codepoint_size == 2) + { + if (!str[1] || + ((str[1] & 0b11000000) != 0b10000000)) + return false; + } + else if ((str[0] & 0b10000000) != 0) + return false; + str += codepoint_size; + } + return true; + } + + std::string remove_invalid_xml_chars(const std::string& original) + { + // The given string MUST be a valid utf-8 string + std::vector res(original.size(), '\0'); + + // pointer where we write valid chars + char* r = res.data(); + + const char* str = original.c_str(); + std::bitset<20> codepoint; + + while (*str) + { + // 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + if ((str[0] & 0b11111000) == 0b11110000) + { + codepoint = ((str[0] & 0b00000111) << 18); + codepoint |= ((str[1] & 0b00111111) << 12); + codepoint |= ((str[2] & 0b00111111) << 6 ); + codepoint |= ((str[3] & 0b00111111) << 0 ); + if (codepoint.to_ulong() <= 0x10FFFF) + { + ::memcpy(r, str, 4); + r += 4; + } + str += 4; + } + // 3 bytes: 1110xxx 10xxxxxx 10xxxxxx + else if ((str[0] & 0b11110000) == 0b11100000) + { + codepoint = ((str[0] & 0b00001111) << 12); + codepoint |= ((str[1] & 0b00111111) << 6); + codepoint |= ((str[2] & 0b00111111) << 0 ); + if (codepoint.to_ulong() <= 0xD7FF || + (codepoint.to_ulong() >= 0xE000 && codepoint.to_ulong() <= 0xFFFD)) + { + ::memcpy(r, str, 3); + r += 3; + } + str += 3; + } + // 2 bytes: 110xxxxx 10xxxxxx + else if (((str[0]) & 0b11100000) == 0b11000000) + { + // All 2 bytes char are valid, don't even bother calculating + // the codepoint + ::memcpy(r, str, 2); + r += 2; + str += 2; + } + // 1 byte: 0xxxxxxx + else if ((str[0] & 0b10000000) == 0) + { + codepoint = ((str[0] & 0b01111111)); + if (codepoint.to_ulong() == 0x09 || + codepoint.to_ulong() == 0x0A || + codepoint.to_ulong() == 0x0D || + codepoint.to_ulong() >= 0x20) + { + ::memcpy(r, str, 1); + r += 1; + } + str += 1; + } + else + throw std::runtime_error("Invalid UTF-8 passed to remove_invalid_xml_chars"); + } + return {res.data(), static_cast(r - res.data())}; + } + + std::string convert_to_utf8(const std::string& str, const char* charset) + { + std::string res; + + const iconv_t cd = iconv_open("UTF-8", charset); + if (cd == (iconv_t)-1) + throw std::runtime_error("Cannot convert into UTF-8"); + + // Make sure cd is always closed when we leave this function + const auto sg = utils::make_scope_guard([&cd](){ iconv_close(cd); }); + + size_t inbytesleft = str.size(); + + // iconv will not attempt to modify this buffer, but some plateform + // require a char** anyway +#ifdef ICONV_SECOND_ARGUMENT_IS_CONST + const char* inbuf_ptr = str.c_str(); +#else + char* inbuf_ptr = const_cast(str.c_str()); +#endif + + size_t outbytesleft = str.size() * 4; + char* outbuf = new char[outbytesleft]; + char* outbuf_ptr = outbuf; + + // Make sure outbuf is always deleted when we leave this function + const auto sg2 = utils::make_scope_guard([outbuf](){ delete[] outbuf; }); + + bool done = false; + while (done == false) + { + size_t error = iconv(cd, &inbuf_ptr, &inbytesleft, &outbuf_ptr, &outbytesleft); + if ((size_t)-1 == error) + { + switch (errno) + { + case EILSEQ: + // Invalid byte found. Insert a placeholder instead of the + // converted character, jump one byte and continue + memcpy(outbuf_ptr, invalid_char, invalid_char_len); + outbuf_ptr += invalid_char_len; + inbytesleft--; + inbuf_ptr++; + break; + case EINVAL: + // A multibyte sequence is not terminated, but we can't + // provide any more data, so we just add a placeholder to + // indicate that the character is not properly converted, + // and we stop the conversion + memcpy(outbuf_ptr, invalid_char, invalid_char_len); + outbuf_ptr += invalid_char_len; + outbuf_ptr++; + done = true; + break; + case E2BIG: // This should never happen + default: // This should happen even neverer + done = true; + break; + } + } + else + { + // The conversion finished without any error, stop converting + done = true; + } + } + // Terminate the converted buffer, and copy that buffer it into the + // string we return + *outbuf_ptr = '\0'; + res = outbuf; + return res; + } + +} + +namespace xep0106 +{ + static const std::map encode_map = { + {' ', "\\20"}, + {'"', "\\22"}, + {'&', "\\26"}, + {'\'',"\\27"}, + {'/', "\\2f"}, + {':', "\\3a"}, + {'<', "\\3c"}, + {'>', "\\3e"}, + {'@', "\\40"}, + }; + + void decode(std::string& s) + { + std::string::size_type pos; + for (const auto& pair: encode_map) + while ((pos = s.find(pair.second)) != std::string::npos) + s.replace(pos, pair.second.size(), + 1, pair.first); + } + + void encode(std::string& s) + { + std::string::size_type pos; + while ((pos = s.find_first_of(" \"&'/:<>@")) != std::string::npos) + { + auto it = encode_map.find(s[pos]); + assert(it != encode_map.end()); + s.replace(pos, 1, it->second); + } + } +} diff --git a/src/utils/encoding.hpp b/src/utils/encoding.hpp new file mode 100644 index 0000000..586edd8 --- /dev/null +++ b/src/utils/encoding.hpp @@ -0,0 +1,43 @@ +#pragma once + + +#include + +namespace utils +{ + /** + * Return the size, in bytes, of the next UTF-8 codepoint, based on + * the given char. + */ + std::size_t get_next_codepoint_size(const unsigned char c); + /** + * Returns true if the given null-terminated string is valid utf-8. + * + * Based on http://en.wikipedia.org/wiki/UTF-8#Description + */ + bool is_valid_utf8(const char* s); + /** + * Remove all invalid codepoints from the given utf-8-encoded string. + * The value returned is a copy of the string, without the removed chars. + * + * See http://www.w3.org/TR/xml/#charsets for the list of valid characters + * in XML. + */ + std::string remove_invalid_xml_chars(const std::string& original); + /** + * Convert the given string (encoded is "encoding") into valid utf-8. + * If some decoding fails, insert an utf-8 placeholder character instead. + */ + std::string convert_to_utf8(const std::string& str, const char* encoding); +} + +namespace xep0106 +{ + /** + * Decode and encode inplace. + */ + void decode(std::string&); + void encode(std::string&); +} + + diff --git a/src/utils/get_first_non_empty.cpp b/src/utils/get_first_non_empty.cpp new file mode 100644 index 0000000..5b3bedb --- /dev/null +++ b/src/utils/get_first_non_empty.cpp @@ -0,0 +1,11 @@ +#include + +bool is_empty(const std::string& val) +{ + return val.empty(); +} + +bool is_empty(const int& val) +{ + return val == 0; +} diff --git a/src/utils/get_first_non_empty.hpp b/src/utils/get_first_non_empty.hpp new file mode 100644 index 0000000..a38f5fb --- /dev/null +++ b/src/utils/get_first_non_empty.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +bool is_empty(const std::string& val); +bool is_empty(const int& val); + +template +T get_first_non_empty(T&& last) +{ + return last; +} + +template +T get_first_non_empty(T&& first, Args&&... args) +{ + if (!is_empty(first)) + return first; + return get_first_non_empty(std::forward(args)...); +} diff --git a/src/utils/revstr.cpp b/src/utils/revstr.cpp new file mode 100644 index 0000000..87fd801 --- /dev/null +++ b/src/utils/revstr.cpp @@ -0,0 +1,9 @@ +#include + +namespace utils +{ + std::string revstr(const std::string& original) + { + return {original.rbegin(), original.rend()}; + } +} diff --git a/src/utils/revstr.hpp b/src/utils/revstr.hpp new file mode 100644 index 0000000..8e521ea --- /dev/null +++ b/src/utils/revstr.hpp @@ -0,0 +1,11 @@ +#pragma once + + +#include + +namespace utils +{ + std::string revstr(const std::string& original); +} + + diff --git a/src/utils/scopeguard.hpp b/src/utils/scopeguard.hpp new file mode 100644 index 0000000..e697fc3 --- /dev/null +++ b/src/utils/scopeguard.hpp @@ -0,0 +1,98 @@ +#pragma once + +#include +#include +#include + +/** + * A class to be used to make sure some functions are called when the scope + * is left, because they will be called in the ScopeGuard's destructor. It + * can for example be used to delete some pointer whenever any exception is + * called. Example: + + * { + * ScopeGuard scope; + * int* number = new int(2); + * scope.add_callback([number]() { delete number; }); + * // Do some other stuff with the number. But these stuff might throw an exception: + * throw std::runtime_error("Some error not caught here, but in our caller"); + * return true; + * } + + * In this example, our pointer will always be deleted, even when the + * exception is thrown. If we want the functions to be called only when the + * scope is left because of an unexpected exception, we can use + * ScopeGuard::disable(); + */ + +namespace utils +{ + +class ScopeGuard +{ +public: + /** + * The constructor can take a callback. But additional callbacks can be + * added later with add_callback() + */ + explicit ScopeGuard(std::function&& func): + enabled(true) + { + this->add_callback(std::move(func)); + } + + ScopeGuard(const ScopeGuard&) = delete; + ScopeGuard& operator=(ScopeGuard&&) = delete; + ScopeGuard(ScopeGuard&&) = delete; + ScopeGuard& operator=(const ScopeGuard&) = delete; + + /** + * default constructor, the scope guard is enabled but empty, use + * add_callback() + */ + explicit ScopeGuard(): + enabled(true) + { + } + /** + * Call all callbacks in the desctructor, unless it has been disabled. + */ + ~ScopeGuard() + { + if (this->enabled) + for (auto& func: this->callbacks) + func(); + } + /** + * Add a callback to be called in our destructor, one scope guard can be + * used for more than one task, if needed. + */ + void add_callback(std::function&& func) + { + this->callbacks.emplace_back(std::move(func)); + } + /** + * Disable that scope guard, nothing will be done when the scope is + * exited. + */ + void disable() + { + this->enabled = false; + } + +private: + bool enabled; + std::vector> callbacks; + +}; + +template +auto make_scope_guard(F f) +{ + static struct Empty {} empty; + auto deleter = [f = std::move(f)](Empty*) { f(); }; + return std::unique_ptr{&empty, std::move(deleter)}; +} + +} + diff --git a/src/utils/sha1.cpp b/src/utils/sha1.cpp new file mode 100644 index 0000000..b77446e --- /dev/null +++ b/src/utils/sha1.cpp @@ -0,0 +1,32 @@ +#include + +#include + +#ifdef BOTAN_FOUND +# include +# include +#endif +#ifdef GCRYPT_FOUND +# include +# include +# include +# include +#endif + +std::string sha1(const std::string& input) +{ +#ifdef BOTAN_FOUND + auto sha1 = Botan::HashFunction::create_or_throw("SHA-1"); + sha1->update(input); + return Botan::hex_encode(sha1->final(), false); +#endif +#ifdef GCRYPT_FOUND + const auto hash_length = gcry_md_get_algo_dlen(GCRY_MD_SHA1); + std::vector output(hash_length, {}); + gcry_md_hash_buffer(GCRY_MD_SHA1, output.data(), input.data(), input.size()); + std::ostringstream digest; + for (std::size_t i = 0; i < hash_length; i++) + digest << std::hex << std::setfill('0') << std::setw(2) << static_cast(output[i]); + return digest.str(); +#endif +} diff --git a/src/utils/sha1.hpp b/src/utils/sha1.hpp new file mode 100644 index 0000000..6c551ac --- /dev/null +++ b/src/utils/sha1.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +std::string sha1(const std::string& input); diff --git a/src/utils/split.cpp b/src/utils/split.cpp new file mode 100644 index 0000000..80f8dae --- /dev/null +++ b/src/utils/split.cpp @@ -0,0 +1,19 @@ +#include +#include + +namespace utils +{ + std::vector split(const std::string& s, const char delim, const bool allow_empty) + { + std::vector ret; + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, delim)) + { + if (item.empty() && !allow_empty) + continue ; + ret.emplace_back(std::move(item)); + } + return ret; + } +} diff --git a/src/utils/split.hpp b/src/utils/split.hpp new file mode 100644 index 0000000..3755ef8 --- /dev/null +++ b/src/utils/split.hpp @@ -0,0 +1,12 @@ +#pragma once + + +#include +#include + +namespace utils +{ + std::vector split(const std::string &s, const char delim, const bool allow_empty=true); +} + + diff --git a/src/utils/string.cpp b/src/utils/string.cpp new file mode 100644 index 0000000..635e71a --- /dev/null +++ b/src/utils/string.cpp @@ -0,0 +1,28 @@ +#include +#include + +bool to_bool(const std::string& val) +{ + return (val == "1" || val == "true"); +} + +std::vector cut(const std::string& val, const std::size_t size) +{ + std::vector res; + std::string::size_type pos = 0; + while (pos < val.size()) + { + // Get the number of chars, <= size, that contain only whole + // UTF-8 codepoints. + std::size_t s = 0; + auto codepoint_size = utils::get_next_codepoint_size(val[pos + s]); + while (s + codepoint_size <= size && pos + s < val.size()) + { + s += codepoint_size; + codepoint_size = utils::get_next_codepoint_size(val[pos + s]); + } + res.emplace_back(val.substr(pos, s)); + pos += s; + } + return res; +} diff --git a/src/utils/string.hpp b/src/utils/string.hpp new file mode 100644 index 0000000..84ba101 --- /dev/null +++ b/src/utils/string.hpp @@ -0,0 +1,10 @@ +#pragma once + + +#include +#include + +bool to_bool(const std::string& val); +std::vector cut(const std::string& val, const std::size_t size); + + diff --git a/src/utils/system.cpp b/src/utils/system.cpp new file mode 100644 index 0000000..c0bee11 --- /dev/null +++ b/src/utils/system.cpp @@ -0,0 +1,21 @@ +#include +#include +#include +#include + +using namespace std::string_literals; + +namespace utils +{ +std::string get_system_name() +{ + struct utsname uts; + const int res = ::uname(&uts); + if (res == -1) + { + log_error("uname failed: ", std::strerror(errno)); + return "Unknown"; + } + return uts.sysname + " "s + uts.release; +} +} \ No newline at end of file diff --git a/src/utils/system.hpp b/src/utils/system.hpp new file mode 100644 index 0000000..7ea1677 --- /dev/null +++ b/src/utils/system.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include + +namespace utils +{ +std::string get_system_name(); +} \ No newline at end of file diff --git a/src/utils/time.cpp b/src/utils/time.cpp new file mode 100644 index 0000000..8fa3fcd --- /dev/null +++ b/src/utils/time.cpp @@ -0,0 +1,70 @@ +#include +#include + +#include +#include +#include + +#include "biboumi.h" + +namespace utils +{ +std::string to_string(const std::time_t& timestamp) +{ + constexpr std::size_t stamp_size = 21; + char date_buf[stamp_size]; + if (std::strftime(date_buf, stamp_size, "%FT%TZ", std::gmtime(×tamp)) != stamp_size - 1) + return ""; + return {std::begin(date_buf), std::end(date_buf) - 1}; +} + +std::time_t parse_datetime(const std::string& stamp) +{ + static const char* format = "%Y-%m-%dT%H:%M:%S"; + std::tm t = {}; +#ifdef HAS_GET_TIME + std::istringstream ss(stamp); + ss.imbue(std::locale("C")); + + std::string timezone; + ss >> std::get_time(&t, format) >> timezone; + if (ss.fail()) + return -1; +#else + /* Y - m - d T H : M : S */ + constexpr std::size_t stamp_size_without_tz = 4 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2; + if (!strptime(stamp.data(), format, &t)) { + return -1; + } + const std::string timezone(stamp.data() + stamp_size_without_tz); +#endif + + if (timezone.empty()) + return -1; + + if (timezone.compare(0, 1, "Z") != 0) + { + std::stringstream tz_ss; + tz_ss << timezone; + int multiplier = -1; + char prefix; + int hours; + char sep; + int minutes; + tz_ss >> prefix >> hours >> sep >> minutes; + if (tz_ss.fail()) + return -1; + if (prefix == '-') + multiplier = +1; + else if (prefix != '+') + return -1; + + t.tm_hour += multiplier * hours; + t.tm_min += multiplier * minutes; + } + return ::timegm(&t); +} + +} + + diff --git a/src/utils/time.hpp b/src/utils/time.hpp new file mode 100644 index 0000000..c71cd9c --- /dev/null +++ b/src/utils/time.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + +namespace utils +{ +std::string to_string(const std::time_t& timestamp); +std::time_t parse_datetime(const std::string& stamp); +} \ No newline at end of file diff --git a/src/utils/timed_events.cpp b/src/utils/timed_events.cpp new file mode 100644 index 0000000..5077199 --- /dev/null +++ b/src/utils/timed_events.cpp @@ -0,0 +1,47 @@ +#include + +TimedEvent::TimedEvent(std::chrono::steady_clock::time_point&& time_point, + std::function callback, const std::string& name): + time_point(std::move(time_point)), + callback(callback), + repeat(false), + repeat_delay(0), + name(name) +{ +} + +TimedEvent::TimedEvent(std::chrono::milliseconds&& duration, + std::function callback, const std::string& name): + time_point(std::chrono::steady_clock::now() + duration), + callback(callback), + repeat(true), + repeat_delay(std::move(duration)), + name(name) +{ +} + +bool TimedEvent::is_after(const TimedEvent& other) const +{ + return this->is_after(other.time_point); +} + +bool TimedEvent::is_after(const std::chrono::steady_clock::time_point& time_point) const +{ + return this->time_point > time_point; +} + +std::chrono::milliseconds TimedEvent::get_timeout() const +{ + auto diff = std::chrono::duration_cast(this->time_point - std::chrono::steady_clock::now()); + return std::max(diff, 0ms); +} + +void TimedEvent::execute() const +{ + this->callback(); +} + +const std::string& TimedEvent::get_name() const +{ + return this->name; +} diff --git a/src/utils/timed_events.hpp b/src/utils/timed_events.hpp new file mode 100644 index 0000000..6e28206 --- /dev/null +++ b/src/utils/timed_events.hpp @@ -0,0 +1,132 @@ +#pragma once + +#include +#include +#include +#include + +using namespace std::literals::chrono_literals; + +namespace utils { +static constexpr std::chrono::milliseconds no_timeout = std::chrono::milliseconds(-1); +} + +class TimedEventsManager; + +/** + * A callback with an associated date. + */ + +class TimedEvent +{ + friend class TimedEventsManager; +public: + /** + * An event the occurs only once, at the given time_point + */ + explicit TimedEvent(std::chrono::steady_clock::time_point&& time_point, + std::function callback, const std::string& name=""); + explicit TimedEvent(std::chrono::milliseconds&& duration, + std::function callback, const std::string& name=""); + + explicit TimedEvent(TimedEvent&&) = default; + TimedEvent& operator=(TimedEvent&&) = default; + ~TimedEvent() = default; + + TimedEvent(const TimedEvent&) = delete; + TimedEvent& operator=(const TimedEvent&) = delete; + + /** + * Whether or not this event happens after the other one. + */ + bool is_after(const TimedEvent& other) const; + bool is_after(const std::chrono::steady_clock::time_point& time_point) const; + /** + * Return the duration difference between now and the event time point. + * If the difference would be negative (i.e. the event is expired), the + * returned value is 0 instead. The value cannot then be negative. + */ + std::chrono::milliseconds get_timeout() const; + void execute() const; + const std::string& get_name() const; + +private: + /** + * The next time point at which the event is executed. + */ + std::chrono::steady_clock::time_point time_point; + /** + * The function to execute. + */ + std::function callback; + /** + * Whether or not this events repeats itself until it is destroyed. + */ + bool repeat; + /** + * This value is added to the time_point each time the event is executed, + * if repeat is true. Otherwise it is ignored. + */ + std::chrono::milliseconds repeat_delay; + /** + * A name that is used to identify that event. If you want to find your + * event (for example if you want to cancel it), the name should be + * unique. + */ + std::string name; +}; + +/** + * A class managing a list of TimedEvents. + * They are sorted, new events can be added, removed, fetch, etc. + */ + +class TimedEventsManager +{ +public: + ~TimedEventsManager() = default; + + TimedEventsManager(const TimedEventsManager&) = delete; + TimedEventsManager(TimedEventsManager&&) = delete; + TimedEventsManager& operator=(const TimedEventsManager&) = delete; + TimedEventsManager& operator=(TimedEventsManager&&) = delete; + + /** + * Return the unique instance of this class + */ + static TimedEventsManager& instance(); + /** + * Add an event to the list of managed events. The list is sorted after + * this call. + */ + void add_event(TimedEvent&& event); + /** + * Returns the duration, in milliseconds, between now and the next + * available event. If the event is already expired (the duration is + * negative), 0 is returned instead (as in “it's not too late, execute it + * now”) + * Returns a negative value if no event is available. + */ + std::chrono::milliseconds get_timeout() const; + /** + * Execute all the expired events (if their expiration time is exactly + * now, or before now). The event is then removed from the list. If the + * event does repeat, its expiration time is updated and it is reinserted + * in the list at the correct position. + * Returns the number of executed events. + */ + std::size_t execute_expired_events(); + /** + * Remove (and thus cancel) all the timed events with the given name. + * Returns the number of canceled events. + */ + std::size_t cancel(const std::string& name); + /** + * Return the number of managed events. + */ + std::size_t size() const; + +private: + std::vector events; + explicit TimedEventsManager() = default; +}; diff --git a/src/utils/timed_events_manager.cpp b/src/utils/timed_events_manager.cpp new file mode 100644 index 0000000..67d61fe --- /dev/null +++ b/src/utils/timed_events_manager.cpp @@ -0,0 +1,73 @@ +#include + +TimedEventsManager& TimedEventsManager::instance() +{ + static TimedEventsManager inst; + return inst; +} + +void TimedEventsManager::add_event(TimedEvent&& event) +{ + for (auto it = this->events.begin(); it != this->events.end(); ++it) + { + if (it->is_after(event)) + { + this->events.emplace(it, std::move(event)); + return; + } + } + this->events.emplace_back(std::move(event)); +} + +std::chrono::milliseconds TimedEventsManager::get_timeout() const +{ + if (this->events.empty()) + return utils::no_timeout; + return this->events.front().get_timeout(); +} + +std::size_t TimedEventsManager::execute_expired_events() +{ + std::size_t count = 0; + const auto now = std::chrono::steady_clock::now(); + for (auto it = this->events.begin(); it != this->events.end();) + { + if (!it->is_after(now)) + { + TimedEvent copy(std::move(*it)); + it = this->events.erase(it); + ++count; + copy.execute(); + if (copy.repeat) + { + copy.time_point += copy.repeat_delay; + this->add_event(std::move(copy)); + } + continue; + } + else + break; + } + return count; +} + +std::size_t TimedEventsManager::cancel(const std::string& name) +{ + std::size_t res = 0; + for (auto it = this->events.begin(); it != this->events.end();) + { + if (it->get_name() == name) + { + it = this->events.erase(it); + res++; + } + else + ++it; + } + return res; +} + +std::size_t TimedEventsManager::size() const +{ + return this->events.size(); +} diff --git a/src/utils/tolower.cpp b/src/utils/tolower.cpp new file mode 100644 index 0000000..3e518bd --- /dev/null +++ b/src/utils/tolower.cpp @@ -0,0 +1,13 @@ +#include + +namespace utils +{ + std::string tolower(const std::string& original) + { + std::string res; + res.reserve(original.size()); + for (const char c: original) + res += static_cast(std::tolower(c)); + return res; + } +} diff --git a/src/utils/tolower.hpp b/src/utils/tolower.hpp new file mode 100644 index 0000000..650e05d --- /dev/null +++ b/src/utils/tolower.hpp @@ -0,0 +1,11 @@ +#pragma once + + +#include + +namespace utils +{ + std::string tolower(const std::string& original); +} + + diff --git a/src/utils/xdg.cpp b/src/utils/xdg.cpp new file mode 100644 index 0000000..b0fa7be --- /dev/null +++ b/src/utils/xdg.cpp @@ -0,0 +1,29 @@ +#include +#include + +#include "biboumi.h" + +std::string xdg_path(const std::string& filename, const char* env_var) +{ + const char* xdg_home = ::getenv(env_var); + if (xdg_home && xdg_home[0] == '/') + return std::string{xdg_home} + "/" PROJECT_NAME "/" + filename; + else + { + const char* home = ::getenv("HOME"); + if (home) + return std::string{home} + "/" ".config" "/" PROJECT_NAME "/" + filename; + else + return filename; + } +} + +std::string xdg_config_path(const std::string& filename) +{ + return xdg_path(filename, "XDG_CONFIG_HOME"); +} + +std::string xdg_data_path(const std::string& filename) +{ + return xdg_path(filename, "XDG_DATA_HOME"); +} diff --git a/src/utils/xdg.hpp b/src/utils/xdg.hpp new file mode 100644 index 0000000..56e11da --- /dev/null +++ b/src/utils/xdg.hpp @@ -0,0 +1,14 @@ +#pragma once + + +#include + +/** + * Returns a path for the given filename, according to the XDG base + * directory specification, see + * http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + */ +std::string xdg_config_path(const std::string& filename); +std::string xdg_data_path(const std::string& filename); + + -- cgit v1.2.3