rpcsx/rpcs3/rpcs3qt/game_compatibility.cpp

264 lines
6.7 KiB
C++
Raw Normal View History

#include "game_compatibility.h"
2020-02-22 20:42:49 +01:00
#include "gui_settings.h"
#include "progress_dialog.h"
#include "curl_handle.h"
2017-12-04 21:59:28 +01:00
2020-03-21 22:17:15 +01:00
#include <QApplication>
2017-12-04 21:59:28 +01:00
#include <QMessageBox>
2020-02-22 20:42:49 +01:00
#include <QJsonDocument>
#include <QThread>
2017-12-04 21:59:28 +01:00
LOG_CHANNEL(compat_log, "Compat");
2017-12-04 21:59:28 +01:00
constexpr auto qstr = QString::fromStdString;
inline std::string sstr(const QString& _in) { return _in.toStdString(); }
size_t curl_write_cb_compat(char* ptr, size_t /*size*/, size_t nmemb, void* userdata)
{
game_compatibility* gm_cmp = reinterpret_cast<game_compatibility*>(userdata);
return gm_cmp->update_buffer(ptr, nmemb);
}
2017-12-04 21:59:28 +01:00
game_compatibility::game_compatibility(std::shared_ptr<gui_settings> settings) : m_xgui_settings(settings)
{
m_filepath = m_xgui_settings->GetSettingsDir() + "/compat_database.dat";
m_url = "https://rpcs3.net/compatibility?api=v1&export";
m_curl = new curl_handle(this);
2020-03-21 22:17:15 +01:00
2017-12-04 21:59:28 +01:00
RequestCompatibility();
2020-03-21 22:17:15 +01:00
// We need this signal in order to update the GUI from the main thread
connect(this, &game_compatibility::signal_buffer_update, this, &game_compatibility::handle_buffer_update);
}
void game_compatibility::handle_buffer_update(int size, int max)
{
if (m_progress_dialog)
{
m_progress_dialog->setMaximum(max);
m_progress_dialog->setValue(size);
QApplication::processEvents();
}
2017-12-04 21:59:28 +01:00
}
size_t game_compatibility::update_buffer(char* data, size_t size)
{
if (m_curl_abort)
{
return 0;
}
const auto old_size = m_curl_buf.size();
const auto new_size = old_size + size;
m_curl_buf.resize(static_cast<int>(new_size));
memcpy(m_curl_buf.data() + old_size, data, size);
2020-03-21 22:17:15 +01:00
int max = m_progress_dialog ? m_progress_dialog->maximum() : 0;
if (m_actual_dwnld_size < 0)
{
if (curl_easy_getinfo(m_curl->get_curl(), CURLINFO_CONTENT_LENGTH_DOWNLOAD, &m_actual_dwnld_size) == CURLE_OK && m_actual_dwnld_size > 0)
{
2020-03-21 22:17:15 +01:00
max = static_cast<int>(m_actual_dwnld_size);
}
}
2020-03-21 22:17:15 +01:00
Q_EMIT signal_buffer_update(static_cast<int>(new_size), max);
return size;
}
bool game_compatibility::ReadJSON(const QJsonObject& json_data, bool after_download)
2017-12-04 21:59:28 +01:00
{
int return_code = json_data["return_code"].toInt();
2017-12-04 21:59:28 +01:00
if (return_code < 0)
{
if (after_download)
2017-12-04 21:59:28 +01:00
{
std::string error_message;
switch (return_code)
2017-12-04 21:59:28 +01:00
{
case -1:
error_message = "Server Error - Internal Error";
break;
case -2:
error_message = "Server Error - Maintenance Mode";
break;
default:
error_message = "Server Error - Unknown Error";
break;
2017-12-04 21:59:28 +01:00
}
compat_log.error("%s: return code %d", error_message, return_code);
Q_EMIT DownloadError(qstr(error_message) + " " + QString::number(return_code));
2017-12-04 21:59:28 +01:00
}
else
2017-12-04 21:59:28 +01:00
{
compat_log.error("Database Error - Invalid: return code %d", return_code);
2017-12-04 21:59:28 +01:00
}
return false;
}
if (!json_data["results"].isObject())
{
compat_log.error("Database Error - No Results found");
return false;
}
2017-12-04 21:59:28 +01:00
m_compat_database.clear();
2017-12-04 21:59:28 +01:00
QJsonObject json_results = json_data["results"].toObject();
2017-12-04 21:59:28 +01:00
// Retrieve status data for every valid entry
for (const auto& key : json_results.keys())
{
if (!json_results[key].isObject())
2017-12-04 21:59:28 +01:00
{
compat_log.error("Database Error - Unusable object %s", sstr(key));
continue;
}
2017-12-04 21:59:28 +01:00
QJsonObject json_result = json_results[key].toObject();
2017-12-04 21:59:28 +01:00
// Retrieve compatibility information from json
compat_status status = Status_Data.at(json_result.value("status").toString("NoResult"));
2017-12-04 21:59:28 +01:00
// Add date if possible
status.date = json_result.value("date").toString();
2017-12-04 21:59:28 +01:00
// Add latest version if possible
status.latest_version = json_result.value("update").toString();
// Add status to map
m_compat_database.emplace(std::pair<std::string, compat_status>(sstr(key), status));
}
2017-12-04 21:59:28 +01:00
return true;
}
2017-12-04 21:59:28 +01:00
void game_compatibility::RequestCompatibility(bool online)
{
2017-12-04 21:59:28 +01:00
if (!online)
{
// Retrieve database from file
QFile file(m_filepath);
if (!file.exists())
{
compat_log.notice("Database file not found: %s", sstr(m_filepath));
2017-12-04 21:59:28 +01:00
return;
}
if (!file.open(QIODevice::ReadOnly))
{
compat_log.error("Database Error - Could not read database from file: %s", sstr(m_filepath));
2017-12-04 21:59:28 +01:00
return;
}
QByteArray data = file.readAll();
file.close();
compat_log.notice("Finished reading database from file: %s", sstr(m_filepath));
2017-12-04 21:59:28 +01:00
// Create new map from database
ReadJSON(QJsonDocument::fromJson(data).object(), online);
return;
}
compat_log.notice("Beginning compatibility database download from: %s", m_url);
2017-12-04 21:59:28 +01:00
// Show Progress
2020-04-07 17:10:04 +02:00
m_progress_dialog = new progress_dialog(tr("Downloading Database"), tr("Please wait..."), tr("Abort"), 0, 100, true);
2017-12-04 21:59:28 +01:00
m_progress_dialog->show();
curl_easy_setopt(m_curl->get_curl(), CURLOPT_URL, m_url.c_str());
curl_easy_setopt(m_curl->get_curl(), CURLOPT_WRITEFUNCTION, curl_write_cb_compat);
curl_easy_setopt(m_curl->get_curl(), CURLOPT_WRITEDATA, this);
curl_easy_setopt(m_curl->get_curl(), CURLOPT_FOLLOWLOCATION, 1);
m_curl_buf.clear();
m_curl_abort = false;
2017-12-04 21:59:28 +01:00
// Handle abort
connect(m_progress_dialog, &QProgressDialog::canceled, [this] { m_curl_abort = true; });
connect(m_progress_dialog, &QProgressDialog::finished, m_progress_dialog, &QProgressDialog::deleteLater);
2017-12-04 21:59:28 +01:00
2020-03-21 22:17:15 +01:00
auto thread = QThread::create([&]
2017-12-04 21:59:28 +01:00
{
const auto result = curl_easy_perform(m_curl->get_curl());
2020-03-21 22:17:15 +01:00
m_curl_result = result == CURLE_OK;
2017-12-04 21:59:28 +01:00
2020-03-21 22:17:15 +01:00
if (!m_curl_result)
{
Q_EMIT DownloadError(qstr("Curl error: ") + qstr(curl_easy_strerror(result)));
}
});
connect(thread, &QThread::finished, this, [this, online]()
{
2017-12-04 21:59:28 +01:00
if (m_progress_dialog)
{
m_progress_dialog->close();
m_progress_dialog = nullptr;
2017-12-04 21:59:28 +01:00
}
2020-03-21 22:17:15 +01:00
if (!m_curl_result)
2018-05-01 03:21:24 +02:00
{
return;
}
compat_log.notice("Database download finished");
2017-12-04 21:59:28 +01:00
// Create new map from database and write database to file if database was valid
if (ReadJSON(QJsonDocument::fromJson(m_curl_buf).object(), online))
2017-12-04 21:59:28 +01:00
{
// We have a new database in map, therefore refresh gamelist to new state
Q_EMIT DownloadFinished();
// Write database to file
QFile file(m_filepath);
if (file.exists())
{
compat_log.notice("Database file found: %s", sstr(m_filepath));
2017-12-04 21:59:28 +01:00
}
if (!file.open(QIODevice::WriteOnly))
{
compat_log.error("Database Error - Could not write database to file: %s", sstr(m_filepath));
2017-12-04 21:59:28 +01:00
return;
}
file.write(m_curl_buf);
2017-12-04 21:59:28 +01:00
file.close();
compat_log.success("Wrote database to file: %s", sstr(m_filepath));
2017-12-04 21:59:28 +01:00
}
2020-03-21 22:17:15 +01:00
});
thread->setObjectName("Compat Update");
thread->start();
2017-12-04 21:59:28 +01:00
// We want to retrieve a new database, therefore refresh gamelist and indicate that
Q_EMIT DownloadStarted();
}
compat_status game_compatibility::GetCompatibility(const std::string& title_id)
2017-12-04 21:59:28 +01:00
{
if (m_compat_database.empty())
{
return Status_Data.at("NoData");
}
else if (m_compat_database.count(title_id) > 0)
{
return m_compat_database[title_id];
}
return Status_Data.at("NoResult");
}
compat_status game_compatibility::GetStatusData(const QString& status)
2017-12-04 21:59:28 +01:00
{
return Status_Data.at(status);
}