fix(api): watchdog con timeout su tutte le richieste — mai piu' attese infinite (v0.5.1)
Sintomo sul device: l'app restava sullo splash in caricamento per sempre. Causa: QNetworkAccessManager non applica timeout — una richiesta che non riceve risposta (rete del telefono, server occupato) lasciava la UI in attesa infinita, senza errori visibili. - ApiClient::armTimeout(): watchdog per reply (abort -> normale percorso d'errore del chiamante). Applicato a: checkAuth (12 s), login (20 s), refreshSessions e openSession (60 s), newSession/chat-start/switchProfile (20-30 s), fetchProfiles (20 s), transcribe (300 s), tts (120 s). Nessun watchdog sullo stream SSE (longevo per design). - checkAuth: imposta lastError anche in errore (messaggio tradotto "Server not reachable (timeout)") e logga indirizzo ed esito - refreshSessions: log della durata (misurato sul probe: il primo fetch dopo un cambio profilo puo' prendere 11-16 s lato server, anche su DB minuscolo — l'app ora lo gestisce senza sembrare bloccata) - SplashPage: rete di sicurezza (se authChecked non arriva entro 15 s si va comunque al login) + log di instradamento - i18n: nuova stringa tradotta it/fr/de
This commit is contained in:
+39
-1
@@ -10,6 +10,7 @@
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QStandardPaths>
|
||||
#include <QTimer>
|
||||
#include <QUrlQuery>
|
||||
|
||||
ApiClient::ApiClient(Settings *settings, QObject *parent)
|
||||
@@ -35,6 +36,25 @@ ApiClient::ApiClient(Settings *settings, QObject *parent)
|
||||
connect(m_settings, &Settings::profileChanged, this, &ApiClient::profileChanged);
|
||||
}
|
||||
|
||||
// QNetworkAccessManager non applica timeout: senza watchdog una richiesta
|
||||
// senza risposta (rete assente, server occupato) lascerebbe la UI in attesa
|
||||
// per sempre. armTimeout aborta la reply dopo N secondi: poi si attiva il
|
||||
// normale percorso d'errore del chiamante (messaggio visibile, stato resettato).
|
||||
void ApiClient::armTimeout(QNetworkReply *reply, int seconds)
|
||||
{
|
||||
QTimer *timer = new QTimer(reply);
|
||||
timer->setSingleShot(true);
|
||||
timer->setInterval(seconds * 1000);
|
||||
connect(timer, &QTimer::timeout, reply, [reply, seconds]() {
|
||||
if (reply->isRunning()) {
|
||||
qDebug() << "[api] timeout dopo" << seconds << "s su" << reply->url().path();
|
||||
reply->abort();
|
||||
}
|
||||
});
|
||||
connect(reply, &QNetworkReply::finished, timer, &QTimer::stop);
|
||||
timer->start();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- helpers --
|
||||
|
||||
QUrl ApiClient::apiUrl(const QString &path) const
|
||||
@@ -132,10 +152,16 @@ void ApiClient::checkAuth()
|
||||
return;
|
||||
}
|
||||
QNetworkReply *reply = m_nam.get(jsonRequest(QStringLiteral("/api/auth/status")));
|
||||
armTimeout(reply, 12); // il controllo d'avvio deve risolversi, sempre
|
||||
qDebug() << "[api] checkAuth ->" << m_settings->baseUrl();
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
reply->deleteLater();
|
||||
const QByteArray body = reply->readAll();
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
const bool wasTimeout = (reply->error() == QNetworkReply::OperationCanceledError);
|
||||
setLastError(wasTimeout ? tr("Server not reachable (timeout)")
|
||||
: errorFromReply(reply, body));
|
||||
qDebug() << "[api] checkAuth FALLITO:" << m_lastError;
|
||||
setLoggedIn(false);
|
||||
emit authChecked(false);
|
||||
return;
|
||||
@@ -149,6 +175,7 @@ void ApiClient::checkAuth()
|
||||
} else {
|
||||
setLoggedIn(false);
|
||||
}
|
||||
qDebug() << "[api] checkAuth:" << (m_loggedIn ? "sessione valida" : "serve il login");
|
||||
emit authChecked(m_loggedIn);
|
||||
});
|
||||
}
|
||||
@@ -165,6 +192,7 @@ void ApiClient::login(const QString &password)
|
||||
body.insert(QStringLiteral("password"), password);
|
||||
QNetworkReply *reply = m_nam.post(jsonRequest(QStringLiteral("/api/auth/login")),
|
||||
QJsonDocument(body).toJson(QJsonDocument::Compact));
|
||||
armTimeout(reply, 20);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
reply->deleteLater();
|
||||
setBusy(false);
|
||||
@@ -210,9 +238,12 @@ void ApiClient::refreshSessions()
|
||||
q.addQueryItem(QStringLiteral("exclude_hidden"), QStringLiteral("1"));
|
||||
url.setQuery(q);
|
||||
QNetworkReply *reply = m_nam.get(QNetworkRequest(url));
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
armTimeout(reply, 60); // il primo fetch dopo un cambio profilo puo' essere lento
|
||||
const qint64 t0 = QDateTime::currentMSecsSinceEpoch();
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply, t0]() {
|
||||
reply->deleteLater();
|
||||
setBusy(false);
|
||||
qDebug() << "[api] sessions in" << (QDateTime::currentMSecsSinceEpoch() - t0) << "ms";
|
||||
const QByteArray body = reply->readAll();
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
setLastError(errorFromReply(reply, body));
|
||||
@@ -238,6 +269,7 @@ QString ApiClient::profile() const
|
||||
void ApiClient::fetchProfiles()
|
||||
{
|
||||
QNetworkReply *reply = m_nam.get(jsonRequest(QStringLiteral("/api/profiles")));
|
||||
armTimeout(reply, 20);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
reply->deleteLater();
|
||||
const QByteArray data = reply->readAll();
|
||||
@@ -279,6 +311,7 @@ void ApiClient::switchProfile(const QString &name)
|
||||
body.insert(QStringLiteral("name"), target);
|
||||
QNetworkReply *reply = m_nam.post(jsonRequest(QStringLiteral("/api/profile/switch")),
|
||||
QJsonDocument(body).toJson(QJsonDocument::Compact));
|
||||
armTimeout(reply, 20);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply, target]() {
|
||||
reply->deleteLater();
|
||||
const QByteArray data = reply->readAll();
|
||||
@@ -307,6 +340,7 @@ void ApiClient::openSession(const QString &id)
|
||||
q.addQueryItem(QStringLiteral("session_id"), id);
|
||||
url.setQuery(q);
|
||||
QNetworkReply *reply = m_nam.get(QNetworkRequest(url));
|
||||
armTimeout(reply, 60);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
reply->deleteLater();
|
||||
setBusy(false);
|
||||
@@ -337,6 +371,7 @@ void ApiClient::newSession()
|
||||
body.insert(QStringLiteral("workspace"), ws);
|
||||
QNetworkReply *reply = m_nam.post(jsonRequest(QStringLiteral("/api/session/new")),
|
||||
QJsonDocument(body).toJson(QJsonDocument::Compact));
|
||||
armTimeout(reply, 30);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
reply->deleteLater();
|
||||
setBusy(false);
|
||||
@@ -374,6 +409,7 @@ void ApiClient::startChat(const QString &sessionId, const QString &message)
|
||||
|
||||
QNetworkReply *reply = m_nam.post(jsonRequest(QStringLiteral("/api/chat/start")),
|
||||
QJsonDocument(body).toJson(QJsonDocument::Compact));
|
||||
armTimeout(reply, 30);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
handleStartReply(reply);
|
||||
});
|
||||
@@ -540,6 +576,7 @@ void ApiClient::transcribeFile(const QString &path)
|
||||
QNetworkRequest req(apiUrl(QStringLiteral("/api/transcribe")));
|
||||
QNetworkReply *reply = m_nam.post(req, multiPart);
|
||||
multiPart->setParent(reply);
|
||||
armTimeout(reply, 300); // la trascrizione puo' richiedere tempo
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
reply->deleteLater();
|
||||
@@ -584,6 +621,7 @@ void ApiClient::speak(const QString &text)
|
||||
body.insert(QStringLiteral("engine"), m_settings->ttsEngine());
|
||||
QNetworkReply *reply = m_nam.post(jsonRequest(QStringLiteral("/api/tts")),
|
||||
QJsonDocument(body).toJson(QJsonDocument::Compact));
|
||||
armTimeout(reply, 120);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
reply->deleteLater();
|
||||
const QByteArray body = reply->readAll();
|
||||
|
||||
@@ -112,6 +112,8 @@ private:
|
||||
void setTranscribing(bool on);
|
||||
void setLastError(const QString &error);
|
||||
void setLoggedIn(bool on);
|
||||
// Watchdog: aborta la reply dopo N secondi (QNAM non applica timeout).
|
||||
void armTimeout(QNetworkReply *reply, int seconds);
|
||||
|
||||
QUrl apiUrl(const QString &path) const;
|
||||
QNetworkRequest jsonRequest(const QString &path, bool post = false) const;
|
||||
|
||||
+2
-2
@@ -21,9 +21,9 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
|
||||
// AppConfigLocation = ~/.config/harbour/hermes (zona persistente della sandbox).
|
||||
app->setOrganizationName(QStringLiteral("harbour"));
|
||||
app->setApplicationName(QStringLiteral("hermes"));
|
||||
app->setApplicationVersion(QStringLiteral("0.5.0"));
|
||||
app->setApplicationVersion(QStringLiteral("0.5.1"));
|
||||
|
||||
qDebug() << "harbour-hermes v0.5.0 build" << __DATE__ << __TIME__;
|
||||
qDebug() << "harbour-hermes v0.5.1 build" << __DATE__ << __TIME__;
|
||||
|
||||
// Traduzioni: it/fr/de da /usr/share/harbour-hermes/translations, in base
|
||||
// alla lingua di sistema (l'inglese e' la lingua sorgente: nessun .qm).
|
||||
|
||||
Reference in New Issue
Block a user