mirror of
https://invent.kde.org/marcoa/a-la-karte.git
synced 2026-03-26 17:03:08 +00:00
219 lines
6.9 KiB
C++
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;
|
|
}
|