Merge branch 'develop' into main
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
commit
c9ef98353c
48
README.adoc
48
README.adoc
|
@ -15,13 +15,55 @@
|
||||||
:uri-curl: https://curl.haxx.se/
|
:uri-curl: https://curl.haxx.se/
|
||||||
|
|
||||||
*{project}* is a C++ wrapper for the Mastodon API. It replaces
|
*{project}* is a C++ wrapper for the Mastodon API. It replaces
|
||||||
link:{uri-mastodon-cpp}[mastodon-cpp].
|
link:{uri-mastodon-cpp}[mastodon-cpp].
|
||||||
|
|
||||||
|
We aim to create a library that is comfortable, yet minimal. All API endpoints
|
||||||
|
from Mastodon and Pleroma are stored in `enum class`es, to counteract typos and
|
||||||
|
make your life easier. The network-facing code is built on
|
||||||
|
link:{uri-curl}[libcurl], a mature and stable library that is available on
|
||||||
|
virtually every operating system. The library does not parse the responses
|
||||||
|
itself, but returns to you the raw data, because we know everyone has their
|
||||||
|
favorite JSON library and we don't want to impose our choice on you!
|
||||||
|
|
||||||
|
== Features
|
||||||
|
|
||||||
|
This is still a work in progress; here is a rough overview of the features:
|
||||||
|
|
||||||
|
* [ ] Requests
|
||||||
|
** [x] `GET` requests.
|
||||||
|
** [x] Streaming `GET` requests.
|
||||||
|
** [ ] `POST` requests.
|
||||||
|
** [ ] `PATCH` requests.
|
||||||
|
** [ ] `PUT` requests.
|
||||||
|
** [ ] `DELETE` requests.
|
||||||
|
* [x] Report maximum allowed character per post.
|
||||||
|
* [ ] Comfortable access to pagination headers.
|
||||||
|
* [ ] Comfortable function to register a new “app” (get an access token).
|
||||||
|
|
||||||
== Usage
|
== Usage
|
||||||
|
|
||||||
Have a look at the link:{uri-reference}[reference].
|
Have a look at the link:{uri-reference}[reference].
|
||||||
|
|
||||||
// === Examples
|
=== Example
|
||||||
|
|
||||||
|
[source,cpp]
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
#include "mastodonpp.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
mastodonpp::Instance instance{"example.com", {}};
|
||||||
|
mastodonpp::Connection connection{instance};
|
||||||
|
auto answer{connection.get(mastodonpp::API::v1::instance)};
|
||||||
|
if (answer)
|
||||||
|
{
|
||||||
|
std::cout << answer << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
link:{uri-reference}/examples.html[More examples] are included in the reference.
|
||||||
|
|
||||||
== Install
|
== Install
|
||||||
|
|
||||||
|
@ -42,7 +84,7 @@ Have a look at the link:{uri-reference}[reference].
|
||||||
* Tested OS: Linux
|
* Tested OS: Linux
|
||||||
* C++ compiler (tested: link:{uri-gcc}[GCC] 7/8/9, link:{uri-lang}[clang] 6/7)
|
* C++ compiler (tested: link:{uri-gcc}[GCC] 7/8/9, link:{uri-lang}[clang] 6/7)
|
||||||
* link:{uri-cmake}[CMake] (at least: 3.9)
|
* link:{uri-cmake}[CMake] (at least: 3.9)
|
||||||
* link:{uri-curl}[curl] (tested: 7.66 / 7.58)
|
* link:{uri-curl}[curl] (at least: 7.32)
|
||||||
* Optional
|
* Optional
|
||||||
** Documentation: link:{uri-doxygen}[Doxygen] (tested: 1.8)
|
** Documentation: link:{uri-doxygen}[Doxygen] (tested: 1.8)
|
||||||
** Tests: link:{uri-catch}[Catch] (tested: 2.5 / 1.2)
|
** Tests: link:{uri-catch}[Catch] (tested: 2.5 / 1.2)
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
find_package(Threads REQUIRED)
|
||||||
|
|
||||||
file(GLOB sources_examples *.cpp)
|
file(GLOB sources_examples *.cpp)
|
||||||
foreach(src ${sources_examples})
|
foreach(src ${sources_examples})
|
||||||
get_filename_component(bin ${src} NAME_WE)
|
get_filename_component(bin ${src} NAME_WE)
|
||||||
add_executable(${bin} ${src})
|
add_executable(${bin} ${src})
|
||||||
target_link_libraries(${bin} PRIVATE ${PROJECT_NAME})
|
target_link_libraries(${bin} PRIVATE ${PROJECT_NAME} Threads::Threads)
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
if(WITH_DOC)
|
if(WITH_DOC)
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
/* This file is part of mastodonpp.
|
||||||
|
* Copyright © 2020 tastytea <tastytea@tastytea.de>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Print information about an instance (/api/v1/instance).
|
||||||
|
|
||||||
|
#include "mastodonpp.hpp"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace masto = mastodonpp;
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
using std::cout;
|
||||||
|
using std::cerr;
|
||||||
|
using std::endl;
|
||||||
|
using std::to_string;
|
||||||
|
using std::string_view;
|
||||||
|
using std::thread;
|
||||||
|
using std::this_thread::sleep_for;
|
||||||
|
using std::vector;
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
const vector<string_view> args(argv, argv + argc);
|
||||||
|
if (args.size() <= 1)
|
||||||
|
{
|
||||||
|
cerr << "Usage: " << args[0] << " <instance hostname>\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Initialize an Instance.
|
||||||
|
masto::Instance instance{args[1], {}};
|
||||||
|
|
||||||
|
// Initialize a Connection.
|
||||||
|
masto::Connection connection{instance};
|
||||||
|
|
||||||
|
// Find out if the streaming service is fine.
|
||||||
|
auto answer{connection.get(masto::API::v1::streaming_health)};
|
||||||
|
if (answer && answer.body == "OK")
|
||||||
|
{
|
||||||
|
// Make a thread, get all public events.
|
||||||
|
thread stream_thread{[&]
|
||||||
|
{
|
||||||
|
answer = connection.get(masto::API::v1::streaming_public);
|
||||||
|
}};
|
||||||
|
|
||||||
|
// Print new events every 2 seconds, for 10 seconds.
|
||||||
|
for (auto counter{0}; counter < 5; ++counter)
|
||||||
|
{
|
||||||
|
cout << "----------------------------------------" << endl;
|
||||||
|
sleep_for(2s);
|
||||||
|
cout << connection.get_new_stream_contents() << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel the stream, …
|
||||||
|
connection.cancel_stream();
|
||||||
|
// … and get the rest of the data.
|
||||||
|
cout << connection.get_new_stream_contents() << endl;
|
||||||
|
stream_thread.join();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (answer.curl_error_code == 0)
|
||||||
|
{
|
||||||
|
// If it is no libcurl error, it must be an HTTP error.
|
||||||
|
cerr << "HTTP status: " << answer.http_status << endl;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Network errors like “Couldn't resolve host.”.
|
||||||
|
cerr << "libcurl error " << to_string(answer.curl_error_code)
|
||||||
|
<< ": " << answer.error_message << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const masto::CURLException &e)
|
||||||
|
{
|
||||||
|
// Only libcurl errors that are not network errors will be thrown.
|
||||||
|
// There went probably something wrong with the initialization.
|
||||||
|
cerr << e.what() << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -312,7 +312,7 @@ public:
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
inline string_view to_string_view() const
|
inline string_view to_string_view() const
|
||||||
{
|
{
|
||||||
return _endpoint_map.at(_endpoint).data();
|
return _endpoint_map.at(_endpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -33,7 +33,12 @@ using std::string;
|
||||||
using std::string_view;
|
using std::string_view;
|
||||||
using std::variant;
|
using std::variant;
|
||||||
|
|
||||||
using endpoint_variant = variant<API::endpoint_type,string>;
|
/*!
|
||||||
|
* @brief An endpoint. Either API::endpoint_type or `std::string_view`.
|
||||||
|
*
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
using endpoint_variant = variant<API::endpoint_type,string_view>;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief Represents a connection to an instance. Used for requests.
|
* @brief Represents a connection to an instance. Used for requests.
|
||||||
|
@ -66,7 +71,7 @@ public:
|
||||||
* })};
|
* })};
|
||||||
* @endcode
|
* @endcode
|
||||||
*
|
*
|
||||||
* @param endpoint Endpoint as API::endpoint_type or `std::string`.
|
* @param endpoint Endpoint as API::endpoint_type or `std::string_view`.
|
||||||
* @param parameters A map of parameters.
|
* @param parameters A map of parameters.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
|
@ -78,12 +83,13 @@ public:
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief Make a HTTP GET call.
|
* @brief Make a HTTP GET call.
|
||||||
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* @code
|
* @code
|
||||||
* auto answer{connection.get("/api/v1/instance")};
|
* auto answer{connection.get("/api/v1/instance")};
|
||||||
* @endcode
|
* @endcode
|
||||||
*
|
*
|
||||||
* @param endpoint Endpoint as API::endpoint_type or `std::string`.
|
* @param endpoint Endpoint as API::endpoint_type or `std::string_view`.
|
||||||
*
|
*
|
||||||
* @since 0.1.0
|
* @since 0.1.0
|
||||||
*/
|
*/
|
||||||
|
@ -93,6 +99,24 @@ public:
|
||||||
return get(endpoint, {});
|
return get(endpoint, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! @copydoc CURLWrapper::set_proxy(string_view)
|
||||||
|
*
|
||||||
|
* Sets also the proxy for the Instance you used to initialize this
|
||||||
|
* Connection.
|
||||||
|
*/
|
||||||
|
void set_proxy(string_view proxy);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief Copy new stream contents and delete the “original”.
|
||||||
|
*
|
||||||
|
* Note that the last event is not necessarily complete, it could happen
|
||||||
|
* that you are calling this function mid-transfer. You have to check the
|
||||||
|
* data integrity yourself.
|
||||||
|
*
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
string get_new_stream_contents();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Instance &_instance;
|
Instance &_instance;
|
||||||
const string_view _baseuri;
|
const string_view _baseuri;
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#include "curl/curl.h"
|
#include "curl/curl.h"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
@ -31,6 +32,7 @@ namespace mastodonpp
|
||||||
{
|
{
|
||||||
|
|
||||||
using std::map;
|
using std::map;
|
||||||
|
using std::mutex;
|
||||||
using std::string;
|
using std::string;
|
||||||
using std::string_view;
|
using std::string_view;
|
||||||
using std::variant;
|
using std::variant;
|
||||||
|
@ -58,13 +60,13 @@ enum class http_method
|
||||||
* parametermap parameters
|
* parametermap parameters
|
||||||
* {
|
* {
|
||||||
* {"id", "12"},
|
* {"id", "12"},
|
||||||
* {"poll[options]", vector<string>{"Yes", "No", "Maybe"}}
|
* {"poll[options]", vector<string_view>{"Yes", "No", "Maybe"}}
|
||||||
* };
|
* };
|
||||||
* @endcode
|
* @endcode
|
||||||
*
|
*
|
||||||
* @since 0.1.0
|
* @since 0.1.0
|
||||||
*/
|
*/
|
||||||
using parametermap = map<string, variant<string, vector<string>>>;
|
using parametermap = map<string_view, variant<string_view, vector<string_view>>>;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief Handles the details of network connections.
|
* @brief Handles the details of network connections.
|
||||||
|
@ -91,10 +93,10 @@ public:
|
||||||
CURLWrapper();
|
CURLWrapper();
|
||||||
|
|
||||||
//! Copy constructor
|
//! Copy constructor
|
||||||
CURLWrapper(const CURLWrapper &other) = default;
|
CURLWrapper(const CURLWrapper &other) = delete;
|
||||||
|
|
||||||
//! Move constructor
|
//! Move constructor
|
||||||
CURLWrapper(CURLWrapper &&other) noexcept = default;
|
CURLWrapper(CURLWrapper &&other) noexcept = delete;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief Cleans up curl and connection.
|
* @brief Cleans up curl and connection.
|
||||||
|
@ -108,10 +110,10 @@ public:
|
||||||
virtual ~CURLWrapper() noexcept;
|
virtual ~CURLWrapper() noexcept;
|
||||||
|
|
||||||
//! Copy assignment operator
|
//! Copy assignment operator
|
||||||
CURLWrapper& operator=(const CURLWrapper &other) = default;
|
CURLWrapper& operator=(const CURLWrapper &other) = delete;
|
||||||
|
|
||||||
//! Move assignment operator
|
//! Move assignment operator
|
||||||
CURLWrapper& operator=(CURLWrapper &&other) noexcept = default;
|
CURLWrapper& operator=(CURLWrapper &&other) noexcept = delete;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief Returns pointer to the CURL easy handle.
|
* @brief Returns pointer to the CURL easy handle.
|
||||||
|
@ -127,7 +129,41 @@ public:
|
||||||
return _connection;
|
return _connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief Set the proxy to use.
|
||||||
|
*
|
||||||
|
* See [CURLOPT_PROXY(3)]
|
||||||
|
* (https://curl.haxx.se/libcurl/c/CURLOPT_PROXY.html).
|
||||||
|
*
|
||||||
|
* @param proxy Examples: "socks4a://127.0.0.1:9050", "http://[::1]:3128".
|
||||||
|
*
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
void set_proxy(string_view proxy);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief Cancel the stream.
|
||||||
|
*
|
||||||
|
* The stream will be cancelled, usually whithin a second. The @link
|
||||||
|
* answer_type::curl_error_code curl_error_code @endlink of the answer will
|
||||||
|
* be set to 42 (`CURLE_ABORTED_BY_CALLBACK`).
|
||||||
|
*
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
void cancel_stream();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
/*!
|
||||||
|
* @brief Mutex for #get_buffer a.k.a. _curl_buffer_body.
|
||||||
|
*
|
||||||
|
* This mutex is locked in `writer_body()` and
|
||||||
|
* Connection::get_new_stream_contents before anything is read or written
|
||||||
|
* from/to _curl_buffer_body.
|
||||||
|
*
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
mutex buffer_mutex;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief Make a HTTP request.
|
* @brief Make a HTTP request.
|
||||||
*
|
*
|
||||||
|
@ -141,19 +177,73 @@ protected:
|
||||||
answer_type make_request(const http_method &method, string uri,
|
answer_type make_request(const http_method &method, string uri,
|
||||||
const parametermap ¶meters);
|
const parametermap ¶meters);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief Returns a reference to the buffer libcurl writes into.
|
||||||
|
*
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
[[nodiscard]]
|
||||||
|
string &get_buffer()
|
||||||
|
{
|
||||||
|
return _curl_buffer_body;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CURL *_connection;
|
CURL *_connection;
|
||||||
char _curl_buffer_error[CURL_ERROR_SIZE];
|
char _curl_buffer_error[CURL_ERROR_SIZE];
|
||||||
string _curl_buffer_headers;
|
string _curl_buffer_headers;
|
||||||
string _curl_buffer_body;
|
string _curl_buffer_body;
|
||||||
|
bool _stream_cancelled;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief libcurl write callback function.
|
* @brief libcurl write callback function.
|
||||||
*
|
*
|
||||||
* @since 0.1.0
|
* @since 0.1.0
|
||||||
*/
|
*/
|
||||||
static int writer(char *data, size_t size, size_t nmemb,
|
size_t writer_body(char *data, size_t size, size_t nmemb);
|
||||||
string *writerData);
|
|
||||||
|
/*!
|
||||||
|
* @brief Wrapper for curl, because it can only call static member
|
||||||
|
* functions.
|
||||||
|
*
|
||||||
|
* <https://curl.haxx.se/docs/faq.html#Using_C_non_static_functions_f>
|
||||||
|
*
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
static inline size_t writer_body_wrapper(char *data, size_t sz,
|
||||||
|
size_t nmemb, void *f)
|
||||||
|
{
|
||||||
|
return static_cast<CURLWrapper*>(f)->writer_body(data, sz, nmemb);
|
||||||
|
}
|
||||||
|
|
||||||
|
//! @copydoc writer_body
|
||||||
|
size_t writer_header(char *data, size_t size, size_t nmemb);
|
||||||
|
|
||||||
|
//! @copydoc writer_body_wrapper
|
||||||
|
static inline size_t writer_header_wrapper(char *data, size_t sz,
|
||||||
|
size_t nmemb, void *f)
|
||||||
|
{
|
||||||
|
return static_cast<CURLWrapper*>(f)->writer_header(data, sz, nmemb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief libcurl transfer info function.
|
||||||
|
*
|
||||||
|
* Used to cancel streams.
|
||||||
|
*
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
int progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
|
||||||
|
curl_off_t ultotal, curl_off_t ulnow);
|
||||||
|
|
||||||
|
//! @copydoc writer_body_wrapper
|
||||||
|
static inline int progress_wrapper(void *f, void *clientp,
|
||||||
|
curl_off_t dltotal, curl_off_t dlnow,
|
||||||
|
curl_off_t ultotal, curl_off_t ulnow)
|
||||||
|
{
|
||||||
|
return static_cast<CURLWrapper*>(f)->progress(clientp, dltotal, dlnow,
|
||||||
|
ultotal, ulnow);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief Setup libcurl connection.
|
* @brief Setup libcurl connection.
|
||||||
|
@ -161,6 +251,16 @@ private:
|
||||||
* @since 0.1.0
|
* @since 0.1.0
|
||||||
*/
|
*/
|
||||||
void setup_curl();
|
void setup_curl();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief Add parameters to URI.
|
||||||
|
*
|
||||||
|
* @param uri Reference to the URI.
|
||||||
|
* @param parameters The parametermap.
|
||||||
|
*
|
||||||
|
* @since 0.1.0
|
||||||
|
*/
|
||||||
|
void add_parameters_to_uri(string &uri, const parametermap ¶meters);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace mastodonpp
|
} // namespace mastodonpp
|
||||||
|
|
|
@ -50,8 +50,7 @@ public:
|
||||||
*
|
*
|
||||||
* @since 0.1.0
|
* @since 0.1.0
|
||||||
*/
|
*/
|
||||||
explicit Instance(const string_view &hostname,
|
explicit Instance(string_view hostname, string_view access_token);
|
||||||
const string_view &access_token);
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief Returns the hostname.
|
* @brief Returns the hostname.
|
||||||
|
|
|
@ -74,21 +74,22 @@
|
||||||
* @section exceptions Exceptions
|
* @section exceptions Exceptions
|
||||||
*
|
*
|
||||||
* Any unrecoverable libcurl error will be thrown as a
|
* Any unrecoverable libcurl error will be thrown as a
|
||||||
* mastodonpp::CURLException. Network errors will **not** be thrown, but
|
* mastodonpp::CURLException. Network errors will not be thrown, but reported
|
||||||
* reported via the return value.
|
* via the return value.
|
||||||
*
|
*
|
||||||
* @section thread_safety Thread safety
|
* @section thread_safety Thread safety
|
||||||
*
|
*
|
||||||
* The first time you construct an @link mastodonpp::Instance Instance @endlink
|
* The first time you construct an @link mastodonpp::Instance Instance @endlink
|
||||||
* or @link mastodonpp::Connection Connection @endlink, [curl_global_init()]
|
* or @link mastodonpp::Connection Connection @endlink, [curl_global_init(3)]
|
||||||
* (https://curl.haxx.se/libcurl/c/curl_global_init.html) is called. When the
|
* (https://curl.haxx.se/libcurl/c/curl_global_init.html) is called. When the
|
||||||
* last @link mastodonpp::Instance Instance @endlink or @link
|
* last @link mastodonpp::Instance Instance @endlink or @link
|
||||||
* mastodonpp::Connection Connection @endlink is destroyed,
|
* mastodonpp::Connection Connection @endlink is destroyed,
|
||||||
* [curl_global_cleanup()]
|
* [curl_global_cleanup(3)]
|
||||||
* (https://curl.haxx.se/libcurl/c/curl_global_cleanup.html) is called. Both
|
* (https://curl.haxx.se/libcurl/c/curl_global_cleanup.html) is called. Both
|
||||||
* are not thread safe.
|
* are not thread safe.
|
||||||
*
|
*
|
||||||
* @example example01_instance_info.cpp
|
* @example example01_instance_info.cpp
|
||||||
|
* @example example02_streaming.cpp
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
find_package(CURL REQUIRED)
|
find_package(CURL 7.32 REQUIRED)
|
||||||
|
|
||||||
# Write version in header.
|
# Write version in header.
|
||||||
configure_file ("version.hpp.in"
|
configure_file ("version.hpp.in"
|
||||||
|
|
|
@ -23,6 +23,8 @@ API::API(const endpoint_type &endpoint)
|
||||||
: _endpoint{endpoint}
|
: _endpoint{endpoint}
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
// TODO: look for a better way.
|
||||||
|
// NOLINTNEXTLINE(cert-err58-cpp)
|
||||||
const map<API::endpoint_type,string_view> API::_endpoint_map
|
const map<API::endpoint_type,string_view> API::_endpoint_map
|
||||||
{
|
{
|
||||||
{v1::apps, "/api/v1/apps"},
|
{v1::apps, "/api/v1/apps"},
|
||||||
|
|
|
@ -29,17 +29,33 @@ Connection::Connection(Instance &instance)
|
||||||
answer_type Connection::get(const endpoint_variant &endpoint,
|
answer_type Connection::get(const endpoint_variant &endpoint,
|
||||||
const parametermap ¶meters)
|
const parametermap ¶meters)
|
||||||
{
|
{
|
||||||
string uri{[&]
|
const string uri{[&]
|
||||||
{
|
{
|
||||||
if (holds_alternative<API::endpoint_type>(endpoint))
|
if (holds_alternative<API::endpoint_type>(endpoint))
|
||||||
{
|
{
|
||||||
return string(_baseuri).append(
|
return string(_baseuri)
|
||||||
API{std::get<API::endpoint_type>(endpoint)}.to_string_view());
|
+= API{std::get<API::endpoint_type>(endpoint)}.to_string_view();
|
||||||
}
|
}
|
||||||
|
return string(std::get<string_view>(endpoint));
|
||||||
return std::get<string>(endpoint);
|
|
||||||
}()};
|
}()};
|
||||||
|
|
||||||
return make_request(http_method::GET, uri, parameters);
|
return make_request(http_method::GET, uri, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Connection::set_proxy(const string_view proxy)
|
||||||
|
{
|
||||||
|
CURLWrapper::set_proxy(proxy);
|
||||||
|
_instance.set_proxy(proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
string Connection::get_new_stream_contents()
|
||||||
|
{
|
||||||
|
buffer_mutex.lock();
|
||||||
|
auto &buffer{get_buffer()};
|
||||||
|
auto buffer_copy{buffer};
|
||||||
|
buffer.clear();
|
||||||
|
buffer_mutex.unlock();
|
||||||
|
return buffer_copy;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace mastodonpp
|
} // namespace mastodonpp
|
||||||
|
|
|
@ -40,6 +40,7 @@ static atomic<uint16_t> curlwrapper_instances{0};
|
||||||
|
|
||||||
CURLWrapper::CURLWrapper()
|
CURLWrapper::CURLWrapper()
|
||||||
: _curl_buffer_error{}
|
: _curl_buffer_error{}
|
||||||
|
, _stream_cancelled(false)
|
||||||
{
|
{
|
||||||
if (curlwrapper_instances == 0)
|
if (curlwrapper_instances == 0)
|
||||||
{
|
{
|
||||||
|
@ -63,9 +64,26 @@ CURLWrapper::~CURLWrapper() noexcept
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CURLWrapper::set_proxy(const string_view proxy)
|
||||||
|
{
|
||||||
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||||
|
CURLcode code = curl_easy_setopt(_connection, CURLOPT_PROXY, proxy);
|
||||||
|
if (code != CURLE_OK)
|
||||||
|
{
|
||||||
|
throw CURLException{code, "Failed to set proxy", _curl_buffer_error};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CURLWrapper::cancel_stream()
|
||||||
|
{
|
||||||
|
_stream_cancelled = true;
|
||||||
|
}
|
||||||
|
|
||||||
answer_type CURLWrapper::make_request(const http_method &method, string uri,
|
answer_type CURLWrapper::make_request(const http_method &method, string uri,
|
||||||
const parametermap ¶meters)
|
const parametermap ¶meters)
|
||||||
{
|
{
|
||||||
|
_stream_cancelled = false;
|
||||||
|
|
||||||
CURLcode code;
|
CURLcode code;
|
||||||
switch (method)
|
switch (method)
|
||||||
{
|
{
|
||||||
|
@ -74,50 +92,7 @@ answer_type CURLWrapper::make_request(const http_method &method, string uri,
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||||
code = curl_easy_setopt(_connection, CURLOPT_HTTPGET, 1L);
|
code = curl_easy_setopt(_connection, CURLOPT_HTTPGET, 1L);
|
||||||
|
|
||||||
for (const auto ¶m : parameters)
|
add_parameters_to_uri(uri, parameters);
|
||||||
{
|
|
||||||
static constexpr array replace_in_uri
|
|
||||||
{
|
|
||||||
"id", "nickname", "nickname_or_id",
|
|
||||||
"hashtag", "permission_group"
|
|
||||||
};
|
|
||||||
if (any_of(replace_in_uri.begin(), replace_in_uri.end(),
|
|
||||||
[¶m](const auto &s) { return s == param.first; }))
|
|
||||||
{
|
|
||||||
const auto pos{uri.find('<')};
|
|
||||||
if (pos != string::npos)
|
|
||||||
{
|
|
||||||
uri.replace(pos, param.first.size() + 2,
|
|
||||||
get<string>(param.second));
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
static bool first{true};
|
|
||||||
if (first)
|
|
||||||
{
|
|
||||||
uri.append("?");
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
uri.append("&");
|
|
||||||
}
|
|
||||||
if (holds_alternative<string>(param.second))
|
|
||||||
{
|
|
||||||
uri.append(param.first + '=' + get<string>(param.second));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (const auto &arg : get<vector<string>>(param.second))
|
|
||||||
{
|
|
||||||
uri.append(param.first + "[]=" + arg);
|
|
||||||
if (arg != *get<vector<string>>(param.second).rbegin())
|
|
||||||
{
|
|
||||||
uri.append("&");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -162,7 +137,8 @@ answer_type CURLWrapper::make_request(const http_method &method, string uri,
|
||||||
|
|
||||||
answer_type answer;
|
answer_type answer;
|
||||||
code = curl_easy_perform(_connection);
|
code = curl_easy_perform(_connection);
|
||||||
if (code == CURLE_OK)
|
if (code == CURLE_OK
|
||||||
|
|| (code == CURLE_ABORTED_BY_CALLBACK && _stream_cancelled))
|
||||||
{
|
{
|
||||||
long http_status; // NOLINT(google-runtime-int)
|
long http_status; // NOLINT(google-runtime-int)
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||||
|
@ -184,17 +160,40 @@ answer_type CURLWrapper::make_request(const http_method &method, string uri,
|
||||||
return answer;
|
return answer;
|
||||||
}
|
}
|
||||||
|
|
||||||
int CURLWrapper::writer(char *data, size_t size, size_t nmemb,
|
size_t CURLWrapper::writer_body(char *data, size_t size, size_t nmemb)
|
||||||
string *writerData)
|
|
||||||
{
|
{
|
||||||
if(writerData == nullptr)
|
if(data == nullptr)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
writerData->append(data, size*nmemb);
|
buffer_mutex.lock();
|
||||||
|
_curl_buffer_body.append(data, size * nmemb);
|
||||||
|
buffer_mutex.unlock();
|
||||||
|
|
||||||
return static_cast<int>(size * nmemb);
|
return size * nmemb;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t CURLWrapper::writer_header(char *data, size_t size, size_t nmemb)
|
||||||
|
{
|
||||||
|
if(data == nullptr)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_curl_buffer_headers.append(data, size * nmemb);
|
||||||
|
|
||||||
|
return size * nmemb;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CURLWrapper::progress(void *, curl_off_t , curl_off_t ,
|
||||||
|
curl_off_t , curl_off_t )
|
||||||
|
{
|
||||||
|
if (_stream_cancelled)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CURLWrapper::setup_curl()
|
void CURLWrapper::setup_curl()
|
||||||
|
@ -205,40 +204,29 @@ void CURLWrapper::setup_curl()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||||
CURLcode code{curl_easy_setopt(_connection, CURLOPT_ERRORBUFFER,
|
curl_easy_setopt(_connection, CURLOPT_ERRORBUFFER, _curl_buffer_error);
|
||||||
_curl_buffer_error)};
|
|
||||||
if (code != CURLE_OK)
|
|
||||||
{
|
|
||||||
throw CURLException{code, "Failed to set error buffer."};
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||||
code = curl_easy_setopt(_connection, CURLOPT_WRITEFUNCTION, writer);
|
curl_easy_setopt(_connection, CURLOPT_WRITEFUNCTION, writer_body_wrapper);
|
||||||
if (code != CURLE_OK)
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||||
{
|
curl_easy_setopt(_connection, CURLOPT_WRITEDATA, this);
|
||||||
throw CURLException{code, "Failed to set writer", _curl_buffer_error};
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||||
code = curl_easy_setopt(_connection, CURLOPT_HEADERDATA,
|
curl_easy_setopt(_connection, CURLOPT_HEADERFUNCTION,
|
||||||
&_curl_buffer_headers);
|
writer_header_wrapper);
|
||||||
if (code != CURLE_OK)
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||||
{
|
curl_easy_setopt(_connection, CURLOPT_HEADERDATA, this);
|
||||||
throw CURLException{code, "Failed to set header data",
|
|
||||||
_curl_buffer_error};
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||||
code = curl_easy_setopt(_connection, CURLOPT_WRITEDATA, &_curl_buffer_body);
|
curl_easy_setopt(_connection, CURLOPT_XFERINFOFUNCTION, progress_wrapper);
|
||||||
if (code != CURLE_OK)
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||||
{
|
curl_easy_setopt(_connection, CURLOPT_XFERINFODATA, this);
|
||||||
throw CURLException{code, "Failed to set write data",
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||||
_curl_buffer_error};
|
curl_easy_setopt(_connection, CURLOPT_NOPROGRESS, 0L);
|
||||||
}
|
|
||||||
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||||
code = curl_easy_setopt(_connection, CURLOPT_USERAGENT,
|
CURLcode code{curl_easy_setopt(_connection, CURLOPT_USERAGENT,
|
||||||
string("mastorss/").append(version).c_str());
|
(string("mastorss/") += version).c_str())};
|
||||||
if (code != CURLE_OK)
|
if (code != CURLE_OK)
|
||||||
{
|
{
|
||||||
throw CURLException{code, "Failed to set User-Agent",
|
throw CURLException{code, "Failed to set User-Agent",
|
||||||
|
@ -256,4 +244,55 @@ void CURLWrapper::setup_curl()
|
||||||
curl_easy_setopt(_connection, CURLOPT_MAXREDIRS, 10L);
|
curl_easy_setopt(_connection, CURLOPT_MAXREDIRS, 10L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CURLWrapper::add_parameters_to_uri(string &uri,
|
||||||
|
const parametermap ¶meters)
|
||||||
|
{
|
||||||
|
// Replace <ID> with the value of parameter “id” and so on.
|
||||||
|
for (const auto ¶m : parameters)
|
||||||
|
{
|
||||||
|
static constexpr array replace_in_uri
|
||||||
|
{
|
||||||
|
"id", "nickname", "nickname_or_id",
|
||||||
|
"hashtag", "permission_group"
|
||||||
|
};
|
||||||
|
if (any_of(replace_in_uri.begin(), replace_in_uri.end(),
|
||||||
|
[¶m](const auto &s) { return s == param.first; }))
|
||||||
|
{
|
||||||
|
const auto pos{uri.find('<')};
|
||||||
|
if (pos != string::npos)
|
||||||
|
{
|
||||||
|
uri.replace(pos, param.first.size() + 2,
|
||||||
|
get<string_view>(param.second));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool first{true};
|
||||||
|
if (first)
|
||||||
|
{
|
||||||
|
uri += "?";
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uri += "&";
|
||||||
|
}
|
||||||
|
if (holds_alternative<string_view>(param.second))
|
||||||
|
{
|
||||||
|
((uri += param.first) += "=") += get<string_view>(param.second);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (const auto &arg : get<vector<string_view>>(param.second))
|
||||||
|
{
|
||||||
|
((uri += param.first) += "[]=") += arg;
|
||||||
|
if (arg != *get<vector<string_view>>(param.second).rbegin())
|
||||||
|
{
|
||||||
|
uri += "&";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace mastodonpp
|
} // namespace mastodonpp
|
||||||
|
|
|
@ -42,7 +42,7 @@ const char *CURLException::what() const noexcept
|
||||||
+ " - " + _message};
|
+ " - " + _message};
|
||||||
if (!_error_buffer.empty())
|
if (!_error_buffer.empty())
|
||||||
{
|
{
|
||||||
error_string.append(" [" + _error_buffer + "]");
|
error_string += " [" + _error_buffer + "]";
|
||||||
}
|
}
|
||||||
return error_string.c_str();
|
return error_string.c_str();
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ namespace mastodonpp
|
||||||
|
|
||||||
using std::stoull;
|
using std::stoull;
|
||||||
|
|
||||||
Instance::Instance(const string_view &hostname, const string_view &access_token)
|
Instance::Instance(const string_view hostname, const string_view access_token)
|
||||||
: _hostname{hostname}
|
: _hostname{hostname}
|
||||||
, _baseuri{"https://" + _hostname}
|
, _baseuri{"https://" + _hostname}
|
||||||
, _access_token{access_token}
|
, _access_token{access_token}
|
||||||
|
|
Loading…
Reference in New Issue