a-la-karte/src/lutrisimporter.cpp

219 lines
6.9 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 A-La-Karte Contributors
#include "lutrisimporter.h"
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QProcess>
#include <QStandardPaths>
LutrisImporter::LutrisImporter(QObject *parent)
: PlatformImporter(parent)
{
}
QString LutrisImporter::platformName() const
{
return QStringLiteral("Lutris");
}
QString LutrisImporter::platformId() const
{
return QStringLiteral("lutris");
}
bool LutrisImporter::isAvailable() const
{
return !findExecutable(QStringLiteral("lutris")).isEmpty() || directoryExists(findLutrisDataPath());
}
QString LutrisImporter::findLutrisDataPath() const
{
QStringList possiblePaths = {expandPath(QStringLiteral("~/.local/share/lutris")), expandPath(QStringLiteral("~/.var/app/net.lutris.Lutris/data/lutris"))};
for (const QString &path : possiblePaths) {
if (directoryExists(path)) {
return path;
}
}
return {};
}
QString LutrisImporter::findLutrisCachePath() const
{
QStringList possiblePaths = {expandPath(QStringLiteral("~/.cache/lutris")), expandPath(QStringLiteral("~/.var/app/net.lutris.Lutris/cache/lutris"))};
for (const QString &path : possiblePaths) {
if (directoryExists(path)) {
return path;
}
}
return {};
}
QUrl LutrisImporter::findCoverImage(const QString &slug) const
{
QString cachePath = findLutrisCachePath();
if (cachePath.isEmpty()) {
return {};
}
// Check coverart directory
QString coverartDir = cachePath + QStringLiteral("/coverart");
QStringList extensions = {QStringLiteral(".jpg"), QStringLiteral(".png"), QStringLiteral(".webp")};
for (const QString &ext : extensions) {
QString coverPath = coverartDir + QStringLiteral("/") + slug + ext;
if (QFile::exists(coverPath)) {
return QUrl::fromLocalFile(coverPath);
}
}
// Check banners directory as fallback
QString bannersDir = cachePath + QStringLiteral("/banners");
for (const QString &ext : extensions) {
QString bannerPath = bannersDir + QStringLiteral("/") + slug + ext;
if (QFile::exists(bannerPath)) {
return QUrl::fromLocalFile(bannerPath);
}
}
return {};
}
QList<Game *> LutrisImporter::importGames()
{
QList<Game *> games;
// Try using lutris CLI first
QString lutrisPath = findExecutable(QStringLiteral("lutris"));
if (!lutrisPath.isEmpty()) {
QProcess process;
process.start(lutrisPath, {QStringLiteral("-lo"), QStringLiteral("--json")});
if (process.waitForStarted(5000) && process.waitForFinished(30000)) {
QByteArray output = process.readAllStandardOutput();
QJsonDocument doc = QJsonDocument::fromJson(output);
if (doc.isArray()) {
QJsonArray gamesArray = doc.array();
int current = 0;
int total = gamesArray.count();
for (const QJsonValue &value : gamesArray) {
if (!value.isObject())
continue;
QJsonObject obj = value.toObject();
QString id = obj[QStringLiteral("id")].toString();
QString name = obj[QStringLiteral("name")].toString();
QString slug = obj[QStringLiteral("slug")].toString();
QString runner = obj[QStringLiteral("runner")].toString();
QString directory = obj[QStringLiteral("directory")].toString();
if (name.isEmpty())
continue;
Game *game = new Game(QStringLiteral("lutris-%1").arg(slug.isEmpty() ? id : slug), name);
game->setPlatform(platformName());
game->setPlatformId(slug.isEmpty() ? id : slug);
game->setLaunchCommand(QStringLiteral("lutris lutris:rungameid/%1").arg(id));
game->setWorkingDirectory(directory);
game->setInstalled(true);
// Find cover
QUrl coverUrl = findCoverImage(slug);
if (coverUrl.isValid()) {
game->setCoverUrl(coverUrl);
}
games.append(game);
current++;
Q_EMIT importProgress(current, total);
}
}
} else {
if (process.state() != QProcess::NotRunning) {
process.terminate();
if (!process.waitForFinished(3000)) {
process.kill();
process.waitForFinished(3000);
}
}
}
}
// If CLI didn't work, try reading the database directly
if (games.isEmpty()) {
QString dataPath = findLutrisDataPath();
if (dataPath.isEmpty()) {
return games;
}
QString gamesDir = dataPath + QStringLiteral("/games");
QDir dir(gamesDir);
if (!dir.exists()) {
return games;
}
QStringList yamlFiles = dir.entryList({QStringLiteral("*.yml")}, QDir::Files);
int current = 0;
int total = yamlFiles.count();
for (const QString &file : yamlFiles) {
QFile gameFile(gamesDir + QStringLiteral("/") + file);
if (!gameFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
continue;
}
// Simple YAML parsing for game config
QString content = QString::fromUtf8(gameFile.readAll());
QString name, slug, runner;
QStringList lines = content.split(QLatin1Char('\n'));
for (const QString &line : lines) {
if (line.startsWith(QLatin1String("name:"))) {
name = line.mid(5).trimmed();
} else if (line.startsWith(QLatin1String("slug:"))) {
slug = line.mid(5).trimmed();
} else if (line.startsWith(QLatin1String("runner:"))) {
runner = line.mid(7).trimmed();
}
}
if (name.isEmpty()) {
// Use filename without extension
name = QFileInfo(file).baseName();
}
if (slug.isEmpty()) {
slug = QFileInfo(file).baseName();
}
Game *game = new Game(QStringLiteral("lutris-%1").arg(slug), name);
game->setPlatform(platformName());
game->setPlatformId(slug);
game->setLaunchCommand(QStringLiteral("lutris lutris:rungame/%1").arg(slug));
game->setInstalled(true);
QUrl coverUrl = findCoverImage(slug);
if (coverUrl.isValid()) {
game->setCoverUrl(coverUrl);
}
games.append(game);
current++;
Q_EMIT importProgress(current, total);
}
}
return games;
}