feat: Add settings page for providers (#353)

This commit is contained in:
Petr Mironychev
2026-05-21 19:30:32 +02:00
committed by GitHub
parent ca3baa7597
commit e193d1e1fa
45 changed files with 3835 additions and 2 deletions

View File

@@ -0,0 +1,165 @@
// Copyright (C) 2024-2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
#include "ProviderInstanceWriter.hpp"
#include <QCoreApplication>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QSaveFile>
#include <QSet>
#include "ProviderInstanceFactory.hpp"
#include "TomlWriter.hpp"
namespace QodeAssist::Providers {
namespace {
struct Tr
{
Q_DECLARE_TR_FUNCTIONS(QtC::QodeAssist)
};
constexpr int kIdentityKeyColumn = 11; // longest key: "description"
constexpr int kLaunchKeyColumn = 15; // longest key: "ready_timeout_s"
void writeIdentityBlock(TomlSerializer::TomlWriter &w, const ProviderInstance &inst)
{
w.setKeyColumnWidth(0);
w.writeInt(QStringLiteral("schema_version"), 1);
w.writeBlankLine();
w.setKeyColumnWidth(kIdentityKeyColumn);
w.writeString(QStringLiteral("name"), inst.name);
w.writeString(QStringLiteral("client_api"), inst.clientApi);
if (!inst.description.isEmpty())
w.writeString(QStringLiteral("description"), inst.description);
w.writeBlankLine();
w.writeString(QStringLiteral("url"), inst.url);
if (!inst.apiKeyRef.isEmpty())
w.writeString(QStringLiteral("api_key_ref"), inst.apiKeyRef);
}
void writeExtrasBlock(TomlSerializer::TomlWriter &w, const QJsonObject &extras)
{
w.writeBlankLine();
w.writeTableHeader(QStringLiteral("extras"));
w.setKeyColumnWidth(0);
w.writeJsonPrimitives(extras);
}
void writeLaunchBlock(TomlSerializer::TomlWriter &w, const LaunchConfig &l)
{
w.writeBlankLine();
w.writeTableHeader(QStringLiteral("launch"));
w.setKeyColumnWidth(kLaunchKeyColumn);
w.writeString(QStringLiteral("command"), l.command);
if (!l.args.isEmpty())
w.writeStringArray(QStringLiteral("args"), l.args);
if (!l.cwd.isEmpty())
w.writeString(QStringLiteral("cwd"), l.cwd);
if (!l.readyUrl.isEmpty())
w.writeString(QStringLiteral("ready_url"), l.readyUrl);
w.writeInt(QStringLiteral("ready_timeout_s"), l.readyTimeout.count());
w.writeBool(QStringLiteral("auto_start"), l.autoStart);
w.writeBool(QStringLiteral("detach"), l.detach);
if (!l.env.isEmpty()) {
w.writeBlankLine();
w.writeTableHeader(QStringLiteral("launch.env"));
w.setKeyColumnWidth(0);
w.writeStringDict(l.env);
}
}
} // namespace
QString ProviderInstanceWriter::toToml(const ProviderInstance &inst)
{
TomlSerializer::TomlWriter w;
writeIdentityBlock(w, inst);
if (!inst.extras.isEmpty())
writeExtrasBlock(w, inst.extras);
if (!inst.launch.isEmpty())
writeLaunchBlock(w, inst.launch);
return w.result();
}
QString ProviderInstanceWriter::deriveBaseName(const QString &name)
{
QString baseName;
for (QChar c : name) {
if (c.isLetterOrNumber())
baseName.append(c.toLower());
else if (c == QLatin1Char(' ') || c == QLatin1Char('-') || c == QLatin1Char('_'))
baseName.append(QLatin1Char('_'));
}
while (baseName.startsWith(QLatin1Char('_')))
baseName.remove(0, 1);
while (baseName.endsWith(QLatin1Char('_')))
baseName.chop(1);
if (baseName.isEmpty())
baseName = QStringLiteral("instance");
return baseName;
}
namespace {
constexpr int kMaxCollisionRetries = 1000;
} // namespace
QString ProviderInstanceWriter::pickUserFilePath(
const QString &userDir, const QString &name, const QString &previousPath)
{
const QDir dir(userDir);
const QString base = deriveBaseName(name);
const QString preferred = dir.filePath(base + QLatin1String(".toml"));
if (!previousPath.isEmpty()
&& QFileInfo(previousPath).absolutePath() == dir.absolutePath()
&& QFileInfo(previousPath).absoluteFilePath() == QFileInfo(preferred).absoluteFilePath())
return preferred;
QSet<QString> taken;
for (const QString &existing : dir.entryList({"*.toml"}, QDir::Files))
taken.insert(existing);
if (!taken.contains(base + QLatin1String(".toml")))
return preferred;
for (int i = 2; i < kMaxCollisionRetries; ++i) {
const QString candidate = QStringLiteral("%1_%2.toml").arg(base).arg(i);
if (!taken.contains(candidate))
return dir.filePath(candidate);
}
return {};
}
QString ProviderInstanceWriter::writeToUserDir(
const ProviderInstance &inst, const QString &previousPath, QString *errorOut)
{
const QString userDir = ProviderInstanceFactory::userInstancesDir();
if (!QDir().mkpath(userDir)) {
if (errorOut)
*errorOut = Tr::tr("Cannot create user provider folder:\n%1").arg(userDir);
return {};
}
const QString filePath = pickUserFilePath(userDir, inst.name, previousPath);
if (filePath.isEmpty()) {
if (errorOut)
*errorOut = Tr::tr("Cannot pick a free filename in:\n%1").arg(userDir);
return {};
}
QSaveFile f(filePath);
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
if (errorOut)
*errorOut = Tr::tr("Cannot write %1:\n%2").arg(filePath, f.errorString());
return {};
}
const QByteArray bytes = toToml(inst).toUtf8();
if (f.write(bytes) != bytes.size() || !f.commit()) {
if (errorOut)
*errorOut = Tr::tr("Write failed for %1:\n%2").arg(filePath, f.errorString());
return {};
}
return filePath;
}
} // namespace QodeAssist::Providers