lolautoaccept/src/datadragon.cpp

328 lines
8.0 KiB
C++

#include "datadragon.h"
#include <ostream>
#include <stdexcept>
#include <curl/easy.h>
#include <Log.h>
#include <QJsonArray>
#include <QJsonObject>
#include <algorithm> // std::max, champ matching
#include "json.h"
static const std::string BASEURL = "https://ddragon.leagueoflegends.com/";
const DataDragon::ChampData DataDragon::EMPTYCHAMP;
DataDragon::DataDragon(const std::string& locale) : RestClient(BASEURL), locale(locale), cache({{"square", ".png"}, {"loading", "_0.jpg"}, {"splash", "_0.jpg"}}) {
startThread();
}
DataDragon::~DataDragon() {
stopAndJoinThread();
}
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;
}
QPixmap DataDragon::getImage(const std::string& champid, ImageType imgtype) {
if(champid.empty()) return {};
// query mem cache
QPixmap img = memcache.getImage(champid, (int) imgtype);
if(!img.isNull()) return img;
// query HDD cache
img = cache[(int) imgtype].getImage(champid);
if(!img.isNull()) {
// 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);
QPixmap decodedImage;
decodedImage.loadFromData(arr);
// 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::vector<const DataDragon::ChampData*> DataDragon::getMatchingChamp(const std::string& name, uint32_t limit) {
if(limit == 0) return {};
if(name.empty()) return {};
getChamps();
std::string lower = toLower(name);
std::vector<size_t> matches;
matches.resize(champs.size());
std::transform(champs.begin(), champs.end(), std::insert_iterator(matches, matches.begin()), std::bind(&matchChamp, std::placeholders::_1, lower));
{
Log::Entry e = Log::info << "matches content: ";
for(size_t m : matches) {
e << m << " ";
}
}
// find smallest element
std::vector<size_t> matches_sort = matches;
std::nth_element(matches_sort.begin(), matches_sort.begin(), matches_sort.end(), [](size_t a, size_t b) { return b < a; });
size_t border = matches_sort.at(0);
{
Log::Entry e = Log::info << "matches_sort content: ";
for(size_t m : matches_sort) {
e << m << " ";
}
}
Log::trace << "border: " << border;
std::vector<const ChampData*> out;
out.reserve(limit);
// take all with a match higher than that
for(uint32_t i = 0; i < matches.size() && out.size() < limit; ++i) {
if(matches[i] >= border) {
out.push_back(&champs[i]);
}
}
return out;
}
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 + "/";
}
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();
}
QPixmap 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 << "]";
}