Copertine: validazione contenuto prima del caching (niente più HTML in cache), id fisso per il dettaglio, handler con sintassi moderna

This commit is contained in:
2026-08-12 13:42:44 +02:00
parent f43521cc67
commit 9d8bd03990
3 changed files with 26 additions and 7 deletions
+4 -3
View File
@@ -172,13 +172,14 @@ Page {
Component.onCompleted: { Component.onCompleted: {
if (bookData.coverUrl && bookData.coverUrl.length > 0) if (bookData.coverUrl && bookData.coverUrl.length > 0)
apiClient.fetchImage(bookData.coverUrl, "detail:" + bookData.id) apiClient.fetchImage(bookData.coverUrl, "detailcover")
} }
Connections { Connections {
target: apiClient target: apiClient
onImageReady: { // nota: parametro rinominato (imageId) — "id" è parola chiave QML e rompe il confronto
if (id === "detail:" + bookData.id) onImageReady: function(imageId, localPath) {
if (imageId === "detailcover")
page.coverSource = localPath page.coverSource = localPath
} }
} }
+2 -2
View File
@@ -175,9 +175,9 @@ Page {
page.loadingMore = false page.loadingMore = false
page.errorMessage = message page.errorMessage = message
} }
onImageReady: { onImageReady: function(imageId, localPath) {
for (var i = 0; i < feedModel.count; i++) { for (var i = 0; i < feedModel.count; i++) {
if (feedModel.get(i).coverId === id) { if (feedModel.get(i).coverId === imageId) {
feedModel.setProperty(i, "coverSource", localPath) feedModel.setProperty(i, "coverSource", localPath)
break break
} }
+20 -2
View File
@@ -127,6 +127,13 @@ void ApiClient::getFeed(const QString &url)
}); });
} }
static bool looksLikeImage(const QByteArray &data)
{
// sniff dei magic byte per PNG/JPEG/GIF/WebP senza dipendere da QtGui
return data.startsWith("\x89PNG") || data.startsWith("\xFF\xD8")
|| data.startsWith("GIF8") || data.startsWith("RIFF");
}
void ApiClient::fetchImage(const QString &url, const QString &id) void ApiClient::fetchImage(const QString &url, const QString &id)
{ {
if (url.isEmpty()) { if (url.isEmpty()) {
@@ -150,16 +157,27 @@ void ApiClient::fetchImage(const QString &url, const QString &id)
} }
QNetworkReply *reply = startGet(imageUrl); QNetworkReply *reply = startGet(imageUrl);
connect(reply, &QNetworkReply::finished, this, [this, reply, id, localPath]() { connect(reply, &QNetworkReply::finished, this, [this, reply, id, localPath, imageUrl]() {
reply->deleteLater(); reply->deleteLater();
const int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); const int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (reply->error() != QNetworkReply::NoError || status >= 400) { if (reply->error() != QNetworkReply::NoError || status >= 400) {
emit imageReady(id, QString()); emit imageReady(id, QString());
return; return;
} }
const QByteArray body = reply->readAll();
const QByteArray contentType = reply->header(
QNetworkRequest::ContentTypeHeader).toByteArray();
// non cachare risposte che non sono immagini (es. pagine HTML di errore 200)
const bool isImage = contentType.startsWith("image/")
|| (contentType.isEmpty() && looksLikeImage(body));
if (!isImage) {
qDebug() << "fetchImage: risposta non immagine scartata" << imageUrl;
emit imageReady(id, QString());
return;
}
QFile file(localPath); QFile file(localPath);
if (file.open(QIODevice::WriteOnly)) { if (file.open(QIODevice::WriteOnly)) {
file.write(reply->readAll()); file.write(body);
file.close(); file.close();
emit imageReady(id, localPath); emit imageReady(id, localPath);
} else { } else {