forked from MrBesen/lolautoaccept
342 lines
8.6 KiB
C++
342 lines
8.6 KiB
C++
#include "datadragon.h"
|
|
|
|
#include <ostream>
|
|
#include <stdexcept>
|
|
|
|
#include <curl/easy.h>
|
|
#include <Log.h>
|
|
|
|
#include <QJsonArray>
|
|
#include <QJsonObject>
|
|
|
|
#include <algorithm> // std::max
|
|
|
|
#include "json.h"
|
|
|
|
static const std::string BASEURL = "https://ddragon.leagueoflegends.com/";
|
|
const DataDragon::ChampData DataDragon::EMPTYCHAMP;
|
|
|
|
static size_t arrayWriteCallback(char* contents, size_t size, size_t nmemb, void* userdata) {
|
|
if(userdata) {
|
|
QByteArray* arr = (QByteArray*) userdata;
|
|
arr->append(contents, size * nmemb);
|
|
return size * nmemb;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
DataDragon::DataDragon(const std::string& locale) : locale(locale), cache({{"square", ".png"}, {"loading", "_0.jpg"}, {"splash", "_0.jpg"}}) {
|
|
curl = curl_easy_init();
|
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, arrayWriteCallback);
|
|
|
|
startThread();
|
|
}
|
|
|
|
DataDragon::~DataDragon() {
|
|
stopAndJoinThread();
|
|
|
|
curl_easy_cleanup(curl);
|
|
}
|
|
|
|
DataDragon::ChampData::ChampData() : key(-1) {}
|
|
|
|
DataDragon::ChampData::ChampData(const QJsonObject& source) {
|
|
name = getValue<std::string>(source, "name", "");
|
|
id = getValue<std::string>(source, "id", "");
|
|
key = getValue<int>(source, "key", -1);
|
|
partype = getValue<std::string>(source, "partype", "");
|
|
title = getValue<std::string>(source, "title", "");
|
|
}
|
|
|
|
const std::string& DataDragon::getVersion() {
|
|
std::unique_lock lock(cachedatamutex);
|
|
while(version.empty() && shouldrun) {
|
|
cachedatacv.wait(lock);
|
|
}
|
|
return version;
|
|
}
|
|
|
|
const std::vector<DataDragon::ChampData>& DataDragon::getChamps() {
|
|
std::unique_lock lock(cachedatamutex);
|
|
while(champs.empty() && shouldrun) {
|
|
cachedatacv.wait(lock);
|
|
}
|
|
return champs;
|
|
}
|
|
|
|
cv::Mat DataDragon::getImage(const std::string& champid, ImageType imgtype) {
|
|
if(champid.empty()) return {};
|
|
|
|
// query mem cache
|
|
cv::Mat img = memcache.getImage(champid, (int) imgtype);
|
|
if(!img.empty()) return img;
|
|
|
|
// query HDD cache
|
|
img = cache[(int) imgtype].getImage(champid);
|
|
if(!img.empty()) {
|
|
// update mem cache
|
|
memcache.addImage(img, champid, (int) imgtype);
|
|
return img;
|
|
}
|
|
|
|
const std::string url = getImageUrl(champid, imgtype);
|
|
if(url.empty()) return {};
|
|
|
|
QByteArray arr = requestRaw(url);
|
|
if(arr.isEmpty()) {
|
|
Log::error << "image could not be loaded";
|
|
return {};
|
|
}
|
|
|
|
// propably an error
|
|
if(arr.size() < 1000) {
|
|
Log::info << "small icon url: " << url;
|
|
Log::info << "content: " << std::string(arr.data(), arr.size());
|
|
return {};
|
|
}
|
|
|
|
// store HDD cache
|
|
cache[(int) imgtype].addImageRaw(arr, champid);
|
|
|
|
// convert byte array to image data
|
|
cv::Mat rawData(1, arr.size(), CV_8UC1, (void*) arr.data());
|
|
|
|
cv::Mat decodedImage = cv::imdecode(rawData, cv::IMREAD_COLOR);
|
|
cv::cvtColor(decodedImage, decodedImage, cv::COLOR_BGR2RGB);
|
|
|
|
// store mem cache
|
|
memcache.addImage(decodedImage, champid, (int) imgtype);
|
|
|
|
return decodedImage;
|
|
}
|
|
|
|
void DataDragon::getImageAsnyc(const std::string& champid, notifyImgfunc_t func, ImageType imgtype) {
|
|
if(!func) return;
|
|
|
|
{
|
|
std::lock_guard lock(tasksmutex);
|
|
tasks.push_back({champid, func, imgtype});
|
|
}
|
|
tasksnotemptycv.notify_one();
|
|
}
|
|
|
|
static std::string toLower(const std::string& in) {
|
|
return QString::fromStdString(in).toLower().toStdString();
|
|
}
|
|
|
|
static size_t startinglength(const std::string& original, const std::string& prefix) {
|
|
size_t i = 0;
|
|
for(; i < original.size() && i < prefix.size(); ++i) {
|
|
if(original.substr(0, i+1) != prefix.substr(0, i+1)) {
|
|
return i;
|
|
}
|
|
}
|
|
return i;
|
|
}
|
|
|
|
static size_t matchChamp(const DataDragon::ChampData& champ, const std::string& lower) {
|
|
std::string lowerid = toLower(champ.id);
|
|
std::string lowername = toLower(champ.name);
|
|
if(lowerid == lower || lowername == lower) {
|
|
return lower.size();
|
|
}
|
|
|
|
size_t lengtha = startinglength(lowerid, lower);
|
|
size_t lengthb = startinglength(lowername, lower);
|
|
|
|
return std::max(lengtha, lengthb);
|
|
}
|
|
|
|
const DataDragon::ChampData& DataDragon::getBestMatchingChamp(const std::string& name, int* count) {
|
|
getChamps();
|
|
// for now: just check for a perfect hit
|
|
std::string lower = toLower(name);
|
|
int bestmatchingoffset = -1;
|
|
size_t bestmatchinglength = 0;
|
|
uint32_t bestmatchingcount = 0;
|
|
for(size_t offset = 0; offset < champs.size(); ++offset) {
|
|
const ChampData& it = champs.at(offset);
|
|
|
|
size_t match = matchChamp(it, lower);
|
|
if(match > bestmatchinglength) {
|
|
bestmatchinglength = match;
|
|
bestmatchingcount = 1;
|
|
bestmatchingoffset = offset;
|
|
} else if(match == bestmatchinglength) {
|
|
bestmatchingcount ++;
|
|
}
|
|
}
|
|
|
|
if(bestmatchinglength < lower.size()) {
|
|
if(count) *count = 0;
|
|
return EMPTYCHAMP;
|
|
}
|
|
|
|
if(count) *count = bestmatchingcount;
|
|
if(bestmatchingoffset >= 0)
|
|
return champs.at(bestmatchingoffset);
|
|
|
|
return EMPTYCHAMP;
|
|
}
|
|
|
|
std::string DataDragon::getImageUrl(const std::string& champid, ImageType type) {
|
|
switch(type) {
|
|
case ImageType::SQUARE: {
|
|
if(getVersion().empty()) {
|
|
return {};
|
|
}
|
|
|
|
// with version
|
|
return getCDNString() + "img/champion/" + champid + ".png";
|
|
}
|
|
case ImageType::LOADING:
|
|
// no version
|
|
return "cdn/img/champion/loading/" + champid + "_0.jpg";
|
|
case ImageType::SPLASH:
|
|
// no version
|
|
return "cdn/img/champion/splash/" + champid + "_0.jpg";
|
|
}
|
|
return {};
|
|
}
|
|
|
|
std::string DataDragon::getCDNString() const {
|
|
return "cdn/" + version + "/";
|
|
}
|
|
|
|
QByteArray DataDragon::requestRaw(const std::string& url) {
|
|
if(!curl) return {};
|
|
|
|
std::string requrl = BASEURL + url;
|
|
QByteArray ba; //buffer
|
|
// std::cout << "[DEBUG] requrl is: " << requrl << std::endl;
|
|
curl_easy_setopt(curl, CURLOPT_URL, requrl.c_str());
|
|
|
|
// curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); //Prevent "longjmp causes uninitialized stack frame" bug
|
|
// set callback data
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ba);
|
|
|
|
// Check for errors
|
|
CURLcode res = curl_easy_perform(curl);
|
|
if (res != CURLE_OK) {
|
|
if(res == CURLE_HTTP_RETURNED_ERROR) {
|
|
long responsecode = -1;
|
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responsecode);
|
|
Log::warn << "DataDragon request failed: " << url << " -> " << responsecode ;
|
|
} else {
|
|
Log::warn << "DataDragon request failed: " << url << " " << curl_easy_strerror(res);
|
|
}
|
|
return {};
|
|
}
|
|
return ba;
|
|
}
|
|
|
|
QJsonDocument DataDragon::request(const std::string& url) {
|
|
QByteArray arr = requestRaw(url);
|
|
if(arr.isEmpty()) return {};
|
|
|
|
QJsonParseError err;
|
|
QJsonDocument parsed = QJsonDocument::fromJson(arr, &err);
|
|
if(parsed.isNull() || err.error != QJsonParseError::NoError) {
|
|
Log::error << "DataDragon Json parse error " << err.errorString().toStdString() << " offset: " << err.offset;
|
|
return {};
|
|
}
|
|
|
|
return parsed;
|
|
}
|
|
|
|
void DataDragon::getVersionInternal() {
|
|
std::unique_lock lock(cachedatamutex);
|
|
|
|
if(!version.empty()) return;
|
|
|
|
QJsonDocument jversions = request("api/versions.json");
|
|
if(jversions.isArray()) {
|
|
QJsonArray jverarr = jversions.array();
|
|
if(!jverarr.empty()) {
|
|
version = jverarr.at(0).toString().toStdString();
|
|
Log::info << "got League version: " << version;
|
|
|
|
lock.unlock();
|
|
cachedatacv.notify_all();
|
|
return;
|
|
}
|
|
}
|
|
Log::error << "error parsing version object";
|
|
}
|
|
|
|
void DataDragon::getChampsInternal() {
|
|
std::unique_lock lock(cachedatamutex);
|
|
if(!champs.empty() && version.empty()) {
|
|
return;
|
|
}
|
|
|
|
QJsonDocument jchamps = request(getCDNString() + "data/" + locale + "/champion.json");
|
|
if(jchamps.isEmpty()) {
|
|
// try again with default locale
|
|
locale = "en_US";
|
|
jchamps = request(getCDNString() + "data/" + locale + "/champion.json");
|
|
}
|
|
if(jchamps.isObject()) {
|
|
QJsonObject obj = jchamps.object();
|
|
auto it = obj.constFind("data");
|
|
if(it != obj.constEnd() && it.value().isObject()) {
|
|
QJsonObject jchampsdata = it.value().toObject();
|
|
for(auto champit = jchampsdata.constBegin(); champit != jchampsdata.constEnd(); champit++) {
|
|
if(champit.value().isObject()) {
|
|
champs.emplace_back(champit.value().toObject());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Log::info << "loaded " << champs.size() << " champs";
|
|
lock.unlock();
|
|
cachedatacv.notify_all();
|
|
}
|
|
|
|
void DataDragon::startThread() {
|
|
shouldrun = true;
|
|
bgthread = std::thread(&DataDragon::threadLoop, this);
|
|
}
|
|
|
|
void DataDragon::stopThread() {
|
|
shouldrun = false;
|
|
tasksnotemptycv.notify_all();
|
|
}
|
|
|
|
void DataDragon::stopAndJoinThread() {
|
|
stopThread();
|
|
|
|
if(bgthread.joinable())
|
|
bgthread.join();
|
|
}
|
|
|
|
void DataDragon::threadLoop() {
|
|
// init version and champ list
|
|
getVersionInternal();
|
|
getChampsInternal();
|
|
|
|
while(shouldrun) {
|
|
// wait for a task in the list
|
|
Task t;
|
|
{
|
|
std::unique_lock lock(tasksmutex);
|
|
if(tasks.empty()) {
|
|
tasksnotemptycv.wait(lock);
|
|
}
|
|
if(tasks.empty()) continue;
|
|
|
|
t = tasks.front();
|
|
tasks.pop_front();
|
|
}
|
|
|
|
cv::Mat img = getImage(t.champid, t.type);
|
|
t.func(img);
|
|
}
|
|
}
|
|
|
|
|
|
std::ostream& operator<<(std::ostream& str, const DataDragon::ChampData& cd) {
|
|
return str << "[n: " << cd.name << " " << " k: " << cd.key << " id: " << cd.id << "]";
|
|
}
|