feat(github): New module

Module used to query the GitHub API for information.
Currently only supports notification count.

Ref #84
This commit is contained in:
Michael Carlberg 2016-12-19 22:01:37 +01:00
parent e72f85079f
commit b417c9f812
13 changed files with 260 additions and 17 deletions

View File

@ -33,6 +33,14 @@ if(NOT DEFINED ENABLE_I3 AND NOT I3_BINARY)
set(ENABLE_I3 OFF CACHE STRING "Module support for i3wm")
endif()
# }}}
# Default value for: ENABLE_CURL {{{
find_package(CURL QUIET)
if(NOT DEFINED ENABLE_CURL AND NOT CURL_FOUND)
set(ENABLE_CURL OFF CACHE STRING "Module support for libcurl")
endif()
# }}}
# Define build options {{{
@ -47,6 +55,7 @@ option(DEBUG_HINTS "Enable hints rendering" OFF)
option(ENABLE_CCACHE "Enable ccache support" OFF)
option(ENABLE_ALSA "Enable alsa support" ON)
option(ENABLE_CURL "Enable curl support" ON)
option(ENABLE_I3 "Enable i3 support" ON)
option(ENABLE_MPD "Enable mpd support" ON)
option(ENABLE_NETWORK "Enable network support" ON)

View File

@ -51,6 +51,7 @@ colored_option(STATUS " Draw debug hints ${DEBUG_HINTS}" DEBUG_HINTS "32;1"
colored_option(STATUS " Enable ccache ${ENABLE_CCACHE}" ENABLE_CCACHE "32;1" "37;2")
message(STATUS "--------------------------")
colored_option(STATUS " Enable alsa ${ENABLE_ALSA}" ENABLE_ALSA "32;1" "37;2")
colored_option(STATUS " Enable curl ${ENABLE_CURL}" ENABLE_CURL "32;1" "37;2")
colored_option(STATUS " Enable i3 ${ENABLE_I3}" ENABLE_I3 "32;1" "37;2")
colored_option(STATUS " Enable mpd ${ENABLE_MPD}" ENABLE_MPD "32;1" "37;2")
colored_option(STATUS " Enable network ${ENABLE_NETWORK}" ENABLE_NETWORK "32;1" "37;2")

View File

@ -5,6 +5,7 @@
#include "components/logger.hpp"
#include "errors.hpp"
#include "utils/env.hpp"
#include "utils/file.hpp"
#include "utils/string.hpp"
#include "x11/xresources.hpp"
@ -196,6 +197,8 @@ class config {
return dereference_env<T>(path.substr(4), fallback);
} else if (path.compare(0, 5, "xrdb:") == 0) {
return dereference_xrdb<T>(path.substr(5), fallback);
} else if (path.compare(0, 5, "file:") == 0) {
return dereference_file<T>(path.substr(5), fallback);
} else if ((pos = path.find(".")) != string::npos) {
return dereference_local<T>(path.substr(0, pos), path.substr(pos + 1), section);
} else {
@ -274,6 +277,21 @@ class config {
return str.empty() ? fallback : convert<T>(move(str));
}
/**
* Dereference file reference by reading its contents
* ${file:/absolute/file/path}
*/
template <typename T>
T dereference_file(string var, const T& fallback) const {
string filename{move(var)};
if (file_util::exists(filename)) {
return convert<T>(string_util::trim(file_util::get_contents(filename), '\n'));
}
return fallback;
}
private:
static constexpr const char* KEY_INHERIT{"inherit"};

View File

@ -16,6 +16,7 @@
#cmakedefine01 ENABLE_MPD
#cmakedefine01 ENABLE_NETWORK
#cmakedefine01 ENABLE_I3
#cmakedefine01 ENABLE_CURL
#cmakedefine01 WITH_XRANDR
#cmakedefine01 WITH_XRENDER
@ -78,6 +79,7 @@ auto print_build_info = [](bool extended = false) {
<< "\n"
<< "Features: "
<< (ENABLE_ALSA ? "+" : "-") << "alsa "
<< (ENABLE_CURL ? "+" : "-") << "curl "
<< (ENABLE_I3 ? "+" : "-") << "i3 "
<< (ENABLE_MPD ? "+" : "-") << "mpd "
<< (ENABLE_NETWORK ? "+" : "-") << "network "

View File

@ -0,0 +1,30 @@
#pragma once
#include "config.hpp"
#include "modules/meta/timer_module.hpp"
#include "utils/http.hpp"
POLYBAR_NS
namespace modules {
/**
* Module used to query the GitHub API for notification count
*/
class github_module : public timer_module<github_module> {
public:
explicit github_module(const bar_settings&, string);
void setup();
bool update();
bool build(builder* builder, const string& tag) const;
private:
static constexpr auto TAG_LABEL = "<label>";
label_t m_label{};
string m_accesstoken{};
unique_ptr<http_downloader> m_http{};
};
}
POLYBAR_NS_END

View File

@ -25,6 +25,9 @@ namespace modules {
thread_.join();
}
}
if (m_mainthread.joinable()) {
m_mainthread.join();
}
}
template <typename Impl>
@ -44,7 +47,7 @@ namespace modules {
template <typename Impl>
bool module<Impl>::running() const {
return m_enabled.load(std::memory_order_relaxed);
return static_cast<bool>(m_enabled);
}
template <typename Impl>
@ -66,23 +69,19 @@ namespace modules {
}
m_log.info("%s: Stopping", name());
m_enabled.store(false, std::memory_order_relaxed);
m_enabled = false;
wakeup();
std::lock_guard<std::mutex> guard_a(m_buildlock);
std::lock_guard<std::mutex> guard_b(m_updatelock);
std::lock(m_buildlock, m_updatelock);
std::lock_guard<std::mutex> guard_a(m_buildlock, std::adopt_lock);
std::lock_guard<std::mutex> guard_b(m_updatelock, std::adopt_lock);
{
CAST_MOD(Impl)->wakeup();
CAST_MOD(Impl)->teardown();
if (m_mainthread.joinable()) {
m_mainthread.join();
if (m_stop_callback) {
m_stop_callback();
}
}
if (m_stop_callback) {
m_stop_callback();
}
}
template <typename Impl>

View File

@ -31,10 +31,13 @@
#if ENABLE_ALSA
#include "modules/volume.hpp"
#endif
#if ENABLE_CURL
#include "modules/github.hpp"
#endif
#if WITH_XKB
#include "modules/xkeyboard.hpp"
#endif
#if not(ENABLE_I3 && ENABLE_MPD && ENABLE_NETWORK && ENABLE_ALSA && WITH_XKB)
#if not(ENABLE_I3 && ENABLE_MPD && ENABLE_NETWORK && ENABLE_ALSA && ENABLE_CURL && WITH_XKB)
#include "modules/unsupported.hpp"
#endif
@ -57,6 +60,8 @@ namespace {
return new cpu_module(forward<Args>(args)...);
} else if (name == "internal/date") {
return new date_module(forward<Args>(args)...);
} else if (name == "internal/github") {
return new github_module(forward<Args>(args)...);
} else if (name == "internal/fs") {
return new fs_module(forward<Args>(args)...);
} else if (name == "internal/memory") {

View File

@ -15,12 +15,15 @@ namespace modules {
void timer_module<Impl>::runner() {
try {
while (CONST_MOD(Impl).running()) {
std::lock_guard<std::mutex> guard(this->m_updatelock);
{
std::lock_guard<std::mutex> guard(this->m_updatelock);
if (CAST_MOD(Impl)->update())
CAST_MOD(Impl)->broadcast();
}
CAST_MOD(Impl)->sleep(m_interval);
if (CONST_MOD(Impl).running()) {
CAST_MOD(Impl)->sleep(m_interval);
}
}
} catch (const module_error& err) {
CAST_MOD(Impl)->halt(err.what());

View File

@ -1,5 +1,5 @@
#pragma once
#if ENABLE_I3 && ENABLE_MPD && ENABLE_NETWORK && ENABLE_ALSA && WITH_XKB
#if ENABLE_I3 && ENABLE_MPD && ENABLE_NETWORK && ENABLE_ALSA && ENABLE_CURL && WITH_XKB
#error "Support has been enabled for all optional modules"
#endif
@ -9,7 +9,7 @@
#if not(ENABLE_ALSA && ENABLE_I3 && ENABLE_MPD)
#include "modules/meta/event_module.inl"
#endif
#if not ENABLE_NETWORK
#if not(ENABLE_NETWORK && ENABLE_CURL)
#include "modules/meta/timer_module.inl"
#endif
#if not WITH_XKB
@ -62,6 +62,9 @@ namespace modules {
#if not ENABLE_ALSA
DEFINE_UNSUPPORTED_MODULE(volume_module, "internal/volume");
#endif
#if not ENABLE_CURL
DEFINE_UNSUPPORTED_MODULE(github_module, "internal/github");
#endif
#if not WITH_XKB
DEFINE_UNSUPPORTED_MODULE(xkeyboard_module, "internal/xkeyboard");
#endif

30
include/utils/http.hpp Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include "common.hpp"
#include "utils/factory.hpp"
POLYBAR_NS
class http_downloader {
public:
http_downloader(int connection_timeout = 5);
~http_downloader();
string get(const string& url);
long response_code();
protected:
static size_t write(void* p, size_t size, size_t bytes, void* stream);
private:
void* m_curl;
};
namespace http_util {
template <typename... Args>
decltype(auto) make_downloader(Args&&... args) {
return factory_util::unique<http_downloader>(forward<Args>(args)...);
}
}
POLYBAR_NS_END

View File

@ -18,6 +18,9 @@ endif()
if(NOT ENABLE_I3)
list(REMOVE_ITEM SOURCES modules/i3.cpp utils/i3.cpp)
endif()
if(NOT ENABLE_CURL)
list(REMOVE_ITEM SOURCES utils/http.cpp modules/github.cpp)
endif()
if(NOT WITH_XCOMPOSITE)
list(REMOVE_ITEM SOURCES x11/composite.cpp)
@ -128,6 +131,16 @@ if(ENABLE_I3)
endif()
# }}}
# Optional dependency: i3ipcpp {{{
if(ENABLE_CURL)
find_package(CURL REQUIRED)
set(APP_LIBRARIES ${APP_LIBRARIES} ${CURL_LIBRARY})
set(APP_INCLUDE_DIRS ${APP_INCLUDE_DIRS} ${CURL_INCLUDE_DIR})
endif()
# }}}
# Create executable target {{{
make_executable(${PROJECT_NAME} SOURCES

79
src/modules/github.cpp Normal file
View File

@ -0,0 +1,79 @@
#include "modules/github.hpp"
#include "drawtypes/label.hpp"
#include "modules/meta/base.inl"
#include "modules/meta/timer_module.inl"
POLYBAR_NS
namespace modules {
template class module<github_module>;
template class timer_module<github_module>;
/**
* Construct module
*/
github_module::github_module(const bar_settings& bar, string name)
: timer_module<github_module>(bar, name), m_http(http_util::make_downloader()) {}
/**
* Bootstrap module
*/
void github_module::setup() {
m_accesstoken = m_conf.get<string>(name(), "token");
m_interval = m_conf.get<chrono::seconds>(name(), "interval", 60s);
m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL});
if (m_formatter->has(TAG_LABEL)) {
m_label = load_optional_label(m_conf, name(), TAG_LABEL, "Notifications: %notifications%");
}
}
/**
* Update module contents
*/
bool github_module::update() {
string content{m_http->get("https://api.github.com/notifications?access_token=" + m_accesstoken)};
long response_code{m_http->response_code()};
switch (response_code) {
case 200:
break;
case 401:
throw module_error("Bad credentials");
case 403:
throw module_error("Maximum number of login attempts exceeded");
default:
throw module_error("Unspecified error (" + to_string(response_code) + ")");
}
size_t pos{0};
size_t notifications{0};
while ((pos = content.find("\"unread\":true", pos + 1)) != string::npos) {
notifications++;
}
if (m_label) {
m_label->reset_tokens();
m_label->replace_token("%notifications%", to_string(notifications));
}
return true;
}
/**
* Build module content
*/
bool github_module::build(builder* builder, const string& tag) const {
if (tag == TAG_LABEL) {
builder->node(m_label);
} else {
return false;
}
return true;
}
}
POLYBAR_NS_END

51
src/utils/http.cpp Normal file
View File

@ -0,0 +1,51 @@
#include <curl/curl.h>
#include <curl/curlbuild.h>
#include <curl/easy.h>
#include <iostream>
#include <sstream>
#include "errors.hpp"
#include "utils/http.hpp"
POLYBAR_NS
http_downloader::http_downloader(int connection_timeout) {
m_curl = curl_easy_init();
curl_easy_setopt(m_curl, CURLOPT_ACCEPT_ENCODING, "deflate");
curl_easy_setopt(m_curl, CURLOPT_CONNECTTIMEOUT, connection_timeout);
curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, true);
curl_easy_setopt(m_curl, CURLOPT_NOSIGNAL, true);
curl_easy_setopt(m_curl, CURLOPT_USERAGENT, "polybar/" GIT_TAG);
curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, http_downloader::write);
}
http_downloader::~http_downloader() {
curl_easy_cleanup(m_curl);
}
string http_downloader::get(const string& url) {
stringstream out{};
curl_easy_setopt(m_curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &out);
auto res = curl_easy_perform(m_curl);
if (res != CURLE_OK) {
throw application_error(curl_easy_strerror(res), res);
}
return out.str();
}
long http_downloader::response_code() {
long code{0};
curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &code);
return code;
}
size_t http_downloader::write(void* p, size_t size, size_t bytes, void* stream) {
string data{static_cast<const char*>(p), size * bytes};
*(static_cast<stringstream*>(stream)) << data << '\n';
return size * bytes;
}
POLYBAR_NS_END