Merge branch 'develop' into main
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
commit
c9ef98353c
48
README.adoc
48
README.adoc
@ -15,13 +15,55 @@
|
||||
:uri-curl: https://curl.haxx.se/
|
||||
|
||||
*{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
|
||||
|
||||
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
|
||||
|
||||
@ -42,7 +84,7 @@ Have a look at the link:{uri-reference}[reference].
|
||||
* Tested OS: Linux
|
||||
* 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-curl}[curl] (tested: 7.66 / 7.58)
|
||||
* link:{uri-curl}[curl] (at least: 7.32)
|
||||
* Optional
|
||||
** Documentation: link:{uri-doxygen}[Doxygen] (tested: 1.8)
|
||||
** Tests: link:{uri-catch}[Catch] (tested: 2.5 / 1.2)
|
||||
|
@ -1,10 +1,12 @@
|
||||
include(GNUInstallDirs)
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
file(GLOB sources_examples *.cpp)
|
||||
foreach(src ${sources_examples})
|
||||
get_filename_component(bin ${src} NAME_WE)
|
||||
add_executable(${bin} ${src})
|
||||
target_link_libraries(${bin} PRIVATE ${PROJECT_NAME})
|
||||
target_link_libraries(${bin} PRIVATE ${PROJECT_NAME} Threads::Threads)
|
||||
endforeach()
|
||||
|
||||
if(WITH_DOC)
|
||||
|
102
examples/example02_streaming.cpp
Normal file
102
examples/example02_streaming.cpp
Normal file
@ -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]]
|
||||
inline string_view to_string_view() const
|
||||
{
|
||||
return _endpoint_map.at(_endpoint).data();
|
||||
return _endpoint_map.at(_endpoint);
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -33,7 +33,12 @@ using std::string;
|
||||
using std::string_view;
|
||||
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.
|
||||
@ -66,7 +71,7 @@ public:
|
||||
* })};
|
||||
* @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.
|
||||
*
|
||||
*
|
||||
@ -78,12 +83,13 @@ public:
|
||||
|
||||
/*!
|
||||
* @brief Make a HTTP GET call.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* auto answer{connection.get("/api/v1/instance")};
|
||||
* @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
|
||||
*/
|
||||
@ -93,6 +99,24 @@ public:
|
||||
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:
|
||||
Instance &_instance;
|
||||
const string_view _baseuri;
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "curl/curl.h"
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
@ -31,6 +32,7 @@ namespace mastodonpp
|
||||
{
|
||||
|
||||
using std::map;
|
||||
using std::mutex;
|
||||
using std::string;
|
||||
using std::string_view;
|
||||
using std::variant;
|
||||
@ -58,13 +60,13 @@ enum class http_method
|
||||
* parametermap parameters
|
||||
* {
|
||||
* {"id", "12"},
|
||||
* {"poll[options]", vector<string>{"Yes", "No", "Maybe"}}
|
||||
* {"poll[options]", vector<string_view>{"Yes", "No", "Maybe"}}
|
||||
* };
|
||||
* @endcode
|
||||
*
|
||||
* @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.
|
||||
@ -91,10 +93,10 @@ public:
|
||||
CURLWrapper();
|
||||
|
||||
//! Copy constructor
|
||||
CURLWrapper(const CURLWrapper &other) = default;
|
||||
CURLWrapper(const CURLWrapper &other) = delete;
|
||||
|
||||
//! Move constructor
|
||||
CURLWrapper(CURLWrapper &&other) noexcept = default;
|
||||
CURLWrapper(CURLWrapper &&other) noexcept = delete;
|
||||
|
||||
/*!
|
||||
* @brief Cleans up curl and connection.
|
||||
@ -108,10 +110,10 @@ public:
|
||||
virtual ~CURLWrapper() noexcept;
|
||||
|
||||
//! Copy assignment operator
|
||||
CURLWrapper& operator=(const CURLWrapper &other) = default;
|
||||
CURLWrapper& operator=(const CURLWrapper &other) = delete;
|
||||
|
||||
//! Move assignment operator
|
||||
CURLWrapper& operator=(CURLWrapper &&other) noexcept = default;
|
||||
CURLWrapper& operator=(CURLWrapper &&other) noexcept = delete;
|
||||
|
||||
/*!
|
||||
* @brief Returns pointer to the CURL easy handle.
|
||||
@ -127,7 +129,41 @@ public:
|
||||
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:
|
||||
/*!
|
||||
* @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.
|
||||
*
|
||||
@ -141,19 +177,73 @@ protected:
|
||||
answer_type make_request(const http_method &method, string uri,
|
||||
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:
|
||||
CURL *_connection;
|
||||
char _curl_buffer_error[CURL_ERROR_SIZE];
|
||||
string _curl_buffer_headers;
|
||||
string _curl_buffer_body;
|
||||
bool _stream_cancelled;
|
||||
|
||||
/*!
|
||||
* @brief libcurl write callback function.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
static int writer(char *data, size_t size, size_t nmemb,
|
||||
string *writerData);
|
||||
size_t writer_body(char *data, size_t size, size_t nmemb);
|
||||
|
||||
/*!
|
||||
* @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.
|
||||
@ -161,6 +251,16 @@ private:
|
||||
* @since 0.1.0
|
||||
*/
|
||||
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
|
||||
|
@ -50,8 +50,7 @@ public:
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
explicit Instance(const string_view &hostname,
|
||||
const string_view &access_token);
|
||||
explicit Instance(string_view hostname, string_view access_token);
|
||||
|
||||
/*!
|
||||
* @brief Returns the hostname.
|
||||
|
@ -74,21 +74,22 @@
|
||||
* @section exceptions Exceptions
|
||||
*
|
||||
* Any unrecoverable libcurl error will be thrown as a
|
||||
* mastodonpp::CURLException. Network errors will **not** be thrown, but
|
||||
* reported via the return value.
|
||||
* mastodonpp::CURLException. Network errors will not be thrown, but reported
|
||||
* via the return value.
|
||||
*
|
||||
* @section thread_safety Thread safety
|
||||
*
|
||||
* 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
|
||||
* last @link mastodonpp::Instance Instance @endlink or @link
|
||||
* 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
|
||||
* are not thread safe.
|
||||
*
|
||||
* @example example01_instance_info.cpp
|
||||
* @example example02_streaming.cpp
|
||||
*/
|
||||
|
||||
/*!
|
||||
|
@ -1,6 +1,6 @@
|
||||
include(GNUInstallDirs)
|
||||
|
||||
find_package(CURL REQUIRED)
|
||||
find_package(CURL 7.32 REQUIRED)
|
||||
|
||||
# Write version in header.
|
||||
configure_file ("version.hpp.in"
|
||||
|
@ -23,6 +23,8 @@ API::API(const endpoint_type &endpoint)
|
||||
: _endpoint{endpoint}
|
||||
{}
|
||||
|
||||
// TODO: look for a better way.
|
||||
// NOLINTNEXTLINE(cert-err58-cpp)
|
||||
const map<API::endpoint_type,string_view> API::_endpoint_map
|
||||
{
|
||||
{v1::apps, "/api/v1/apps"},
|
||||
|
@ -29,17 +29,33 @@ Connection::Connection(Instance &instance)
|
||||
answer_type Connection::get(const endpoint_variant &endpoint,
|
||||
const parametermap ¶meters)
|
||||
{
|
||||
string uri{[&]
|
||||
const string uri{[&]
|
||||
{
|
||||
if (holds_alternative<API::endpoint_type>(endpoint))
|
||||
{
|
||||
return string(_baseuri).append(
|
||||
API{std::get<API::endpoint_type>(endpoint)}.to_string_view());
|
||||
return string(_baseuri)
|
||||
+= API{std::get<API::endpoint_type>(endpoint)}.to_string_view();
|
||||
}
|
||||
|
||||
return std::get<string>(endpoint);
|
||||
return string(std::get<string_view>(endpoint));
|
||||
}()};
|
||||
|
||||
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
|
||||
|
@ -40,6 +40,7 @@ static atomic<uint16_t> curlwrapper_instances{0};
|
||||
|
||||
CURLWrapper::CURLWrapper()
|
||||
: _curl_buffer_error{}
|
||||
, _stream_cancelled(false)
|
||||
{
|
||||
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,
|
||||
const parametermap ¶meters)
|
||||
{
|
||||
_stream_cancelled = false;
|
||||
|
||||
CURLcode code;
|
||||
switch (method)
|
||||
{
|
||||
@ -74,50 +92,7 @@ answer_type CURLWrapper::make_request(const http_method &method, string uri,
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||
code = curl_easy_setopt(_connection, CURLOPT_HTTPGET, 1L);
|
||||
|
||||
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>(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("&");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
add_parameters_to_uri(uri, parameters);
|
||||
|
||||
break;
|
||||
}
|
||||
@ -162,7 +137,8 @@ answer_type CURLWrapper::make_request(const http_method &method, string uri,
|
||||
|
||||
answer_type answer;
|
||||
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)
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||
@ -184,17 +160,40 @@ answer_type CURLWrapper::make_request(const http_method &method, string uri,
|
||||
return answer;
|
||||
}
|
||||
|
||||
int CURLWrapper::writer(char *data, size_t size, size_t nmemb,
|
||||
string *writerData)
|
||||
size_t CURLWrapper::writer_body(char *data, size_t size, size_t nmemb)
|
||||
{
|
||||
if(writerData == nullptr)
|
||||
if(data == nullptr)
|
||||
{
|
||||
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()
|
||||
@ -205,40 +204,29 @@ void CURLWrapper::setup_curl()
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||
CURLcode code{curl_easy_setopt(_connection, CURLOPT_ERRORBUFFER,
|
||||
_curl_buffer_error)};
|
||||
if (code != CURLE_OK)
|
||||
{
|
||||
throw CURLException{code, "Failed to set error buffer."};
|
||||
}
|
||||
curl_easy_setopt(_connection, CURLOPT_ERRORBUFFER, _curl_buffer_error);
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||
code = curl_easy_setopt(_connection, CURLOPT_WRITEFUNCTION, writer);
|
||||
if (code != CURLE_OK)
|
||||
{
|
||||
throw CURLException{code, "Failed to set writer", _curl_buffer_error};
|
||||
}
|
||||
curl_easy_setopt(_connection, CURLOPT_WRITEFUNCTION, writer_body_wrapper);
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||
curl_easy_setopt(_connection, CURLOPT_WRITEDATA, this);
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||
code = curl_easy_setopt(_connection, CURLOPT_HEADERDATA,
|
||||
&_curl_buffer_headers);
|
||||
if (code != CURLE_OK)
|
||||
{
|
||||
throw CURLException{code, "Failed to set header data",
|
||||
_curl_buffer_error};
|
||||
}
|
||||
curl_easy_setopt(_connection, CURLOPT_HEADERFUNCTION,
|
||||
writer_header_wrapper);
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||
curl_easy_setopt(_connection, CURLOPT_HEADERDATA, this);
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||
code = curl_easy_setopt(_connection, CURLOPT_WRITEDATA, &_curl_buffer_body);
|
||||
if (code != CURLE_OK)
|
||||
{
|
||||
throw CURLException{code, "Failed to set write data",
|
||||
_curl_buffer_error};
|
||||
}
|
||||
curl_easy_setopt(_connection, CURLOPT_XFERINFOFUNCTION, progress_wrapper);
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||
curl_easy_setopt(_connection, CURLOPT_XFERINFODATA, this);
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||
curl_easy_setopt(_connection, CURLOPT_NOPROGRESS, 0L);
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
|
||||
code = curl_easy_setopt(_connection, CURLOPT_USERAGENT,
|
||||
string("mastorss/").append(version).c_str());
|
||||
CURLcode code{curl_easy_setopt(_connection, CURLOPT_USERAGENT,
|
||||
(string("mastorss/") += version).c_str())};
|
||||
if (code != CURLE_OK)
|
||||
{
|
||||
throw CURLException{code, "Failed to set User-Agent",
|
||||
@ -256,4 +244,55 @@ void CURLWrapper::setup_curl()
|
||||
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
|
||||
|
@ -42,7 +42,7 @@ const char *CURLException::what() const noexcept
|
||||
+ " - " + _message};
|
||||
if (!_error_buffer.empty())
|
||||
{
|
||||
error_string.append(" [" + _error_buffer + "]");
|
||||
error_string += " [" + _error_buffer + "]";
|
||||
}
|
||||
return error_string.c_str();
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ namespace mastodonpp
|
||||
|
||||
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}
|
||||
, _baseuri{"https://" + _hostname}
|
||||
, _access_token{access_token}
|
||||
|
Loading…
x
Reference in New Issue
Block a user