geostat/okapi.cpp

264 wiersze
8.1 KiB
C++

#include "okapi.h"
#include "debug.h"
#include <iostream>
#include <iomanip>
#include <sstream>
#include <algorithm>
#include <curl/curl.h>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
static const std::string OKAPI_logs = "services/logs/userlogs";
static const std::string OKAPI_cache = "services/caches/geocache";
static const std::string OKAPI_caches = "services/caches/geocaches";
static const std::string OKAPI_username = "services/users/by_username";
static const std::string OKAPI_user = "services/users/user";
static const std::string OKAPI_changelog = "services/replicate/changelog";
Okapi::Okapi(const std::string& server_url, const std::string& consumer_key) : url(server_url), key(consumer_key) {
if (url.find(".pl/") != std::string::npos)
service = "OC.pl";
if (url.find(".de/") != std::string::npos)
service = "OC.de";
if (url.find(".us/") != std::string::npos)
service = "OC.us";
if (url.find(".nl/") != std::string::npos)
service = "OC.nl";
if (url.find(".ro/") != std::string::npos)
service = "OC.ro";
if (url.find(".uk/") != std::string::npos)
service = "OC.uk";
}
// Callback for cURL easy interface used to save received output to std::string object
size_t Okapi::write_cb(char* ptr, __attribute__((unused)) size_t size, size_t nmemb, void* userdata) {
std::string* str = reinterpret_cast<std::string*>(userdata);
str->append(ptr, nmemb);
return nmemb;
}
std::string Okapi::curl_post(const std::string& url, const std::string& post) const {
CURL* curl;
CURLcode res;
std::string output;
Debug(5) << "API query: " << post;
curl = curl_easy_init();
if (!curl) {
curl_global_cleanup();
throw 0;
}
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&output);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5);
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
Debug(1) << "Connection failed: " << curl_easy_strerror(res) << '\n';
curl_easy_cleanup(curl);
std::exit(EXIT_FAILURE);
}
curl_easy_cleanup(curl);
Debug(5) << "API query result: " << output;
if (output.starts_with("{\"error\"")) {
json j = json::parse(output);
Debug(1) << "OKAPI error: " << j["error"]["developer_message"];
std::exit(EXIT_FAILURE);
}
return output;
}
std::string Okapi::get_user_caches_json(const std::string& uuid, int count, int offset) const {
std::string service = url + OKAPI_logs;
std::string query = "consumer_key=" + key + "&user_uuid=" + uuid + "&fields=cache_code|type|date|was_recommended&limit=" + std::to_string(count) + "&offset=" + std::to_string(offset);
return curl_post(service, query);
}
// std::string Okapi::get_cache_json(std::string code) {
// std::string service = url + OKAPI_cache;
// std::string query = "consumer_key=" + key + "&cache_code=" + code + "&fields=code|name|location|type|status|difficulty|terrain";
// return curl_post(service, query);
// }
std::string Okapi::get_caches_json(const std::string& codes) const {
std::string service = url + OKAPI_caches;
std::string query = "consumer_key=" + key + "&cache_codes=" + codes + "&fields=code|name|location|type|status|difficulty|terrain|owner|region|size2|date_created|recommendations|founds|status";
return curl_post(service, query);
}
// Cache Okapi::get_cache(std::string code) {
// std::string output = get_cache_json(code);
// json j = json::parse(output);
//
// Cache c;
// c.code = code;
// c.name = j["name"];
// c.type = j["type"];
// c.diff = j["difficulty"].get<float>();
// c.terr = j["terrain"].get<float>();
// // std::cout << j["difficulty"] << '\n';
// // std::cout << j["difficulty"].get<float>() << '\n';
// // std::cout << j["difficulty"].get<double>() << '\n';
// // std::cout << j["difficulty"].get<int>() << '\n';
//
// std::string loc = j["location"];
// int pos = loc.find("|");
// //c.pos.lat = stof(loc.substr(0, pos));
// //c.pos.lon = stof(loc.substr(pos + 1));
//
// return c;
// }
Caches Okapi::get_caches(const std::set<std::string>& codes) const {
Cache c;
Caches cc;
uint k;
std::string codes_list;
codes_list.reserve(MAX_CACHES * 8); // maximum of MAX_CACHES codes, 7 chars per code plus a separator
auto it = codes.begin();
while (it != codes.end()) {
k = 0;
while (it != codes.end() && k < MAX_CACHES) {
codes_list += *it;
codes_list += '|';
it++;
k++;
}
codes_list.pop_back(); // remove trailing '|'
std::string output = get_caches_json(codes_list);
json j = json::parse(output);
codes_list.clear();
for (auto& el : j.items()) {
c.code = el.value()["code"];
c.name = el.value()["name"];
c.type = el.value()["type"];
c.size = el.value()["size2"];
c.region = el.value()["region"];
c.diff = el.value()["difficulty"];
c.terr = el.value()["terrain"];
c.owner = el.value()["owner"]["username"];
c.owner_uuid = el.value()["owner"]["uuid"];
c.pos = get_lat_lon(el.value()["location"]);
c.origin = service;
c.fav = el.value()["recommendations"];
c.founds = el.value()["founds"];
if (el.value()["status"] == "Available")
c.status = ok;
else if (el.value()["status"] == "Temporarily unavailable")
c.status = disabled;
else if (el.value()["status"] == "Archived")
c.status = archived;
else
c.status = unknown;
std::tm tmp;
std::stringstream ss(el.value()["date_created"].get<std::string>());
ss >> std::get_time(&tmp, "%Y-%m-%dT%H:%M:%S+");
c.set_date_hidden(tmp);
cc.push_back(c);
}
}
return cc;
}
Caches Okapi::get_user_caches(const std::string& uuid, int count) const {
Caches cc;
std::set<std::string> codes;
struct extra_data {
std::tm date;
bool was_recommended;
};
std::map<std::string, extra_data> tmp_data; // holds data from logs which needs to be added to Caches later
json j;
std::tm date;
int off = 0;
if (count == 0)
do {
std::string output = get_user_caches_json(uuid, MAX_LOGS, off);
j = json::parse(output);
for (auto& el : j.items()) {
if (el.value()["type"] == "Found it") {
std::stringstream ss(el.value()["date"].get<std::string>());
// TODO need to take care of the time zone :/
ss >> std::get_time(&date, "%Y-%m-%dT%H:%M:%S+");
codes.insert(el.value()["cache_code"].get<std::string>());
tmp_data[el.value()["cache_code"].get<std::string>()] = { date, el.value()["was_recommended"] };
}
}
off += j.size();
} while (j.size() > 0);
else {
int count_req = (count > MAX_LOGS) ? MAX_LOGS : count;
do {
std::string output = get_user_caches_json(uuid, count_req, off);
j = json::parse(output);
for (auto& el : j.items()) {
if (el.value()["type"] == "Found it") {
std::stringstream ss(el.value()["date"].get<std::string>());
ss >> std::get_time(&date, "%Y-%m-%dT%H-%M-%S");
codes.insert(el.value()["cache_code"].get<std::string>());
tmp_data[el.value()["cache_code"].get<std::string>()] = { date, el.value()["was_recommended"] };
}
}
off += j.size();
count -= count_req;
} while (j.size() > 0 && count > 0);
}
cc = get_caches(codes);
Debug(2) << "Caches read from OC: " << cc.size() << '\n';
std::map<std::string, Cache&> mcc;
for (auto& i : cc)
mcc.insert( { i.code, i } );
// introduce extra data from the logs to Caches
for (auto& el : tmp_data) {
auto& it = mcc.at(el.first);
it.set_date(el.second.date);
it.recommended = el.second.was_recommended;
}
return cc;
}
std::string Okapi::get_uuid(const std::string& username) const {
std::string service = url + OKAPI_username;
std::string query = "consumer_key=" + key + "&username=" + username + "&fields=uuid";
json j = json::parse(curl_post(service, query));
return j["uuid"];
}
std::string Okapi::get_profile_url(const std::string& uuid) const {
std::string service = url + OKAPI_user;
std::string query = "consumer_key=" + key + "&user_uuid=" + uuid + "&fields=profile_url";
json j = json::parse(curl_post(service, query));
return j["profile_url"];
}
std::string Okapi::get_changelog_json(int revision) const {
std::string service = url + OKAPI_changelog;
std::string query = "consumer_key=" + key + "&since=" + std::to_string(revision);
return curl_post(service, query);
}