Rimossa la sync server dell'avanzamento di lettura (login di sessione, postBookmark, fetchBookmark): resta solo il salvataggio locale della posizione (QSettings) con ripristino locale all'apertura. ApiClient riportato a feed OPDS + copertine
This commit is contained in:
@@ -15,7 +15,6 @@ Page {
|
|||||||
property bool ready: false
|
property bool ready: false
|
||||||
property bool restoring: false
|
property bool restoring: false
|
||||||
property int saveCounter: 0
|
property int saveCounter: 0
|
||||||
property int syncCounter: 0
|
|
||||||
property string errorMessage: ""
|
property string errorMessage: ""
|
||||||
// reflow: scala il font (il testo si ri-avvolge), non lo zoom grafico
|
// reflow: scala il font (il testo si ri-avvolge), non lo zoom grafico
|
||||||
property double fontScale: 1.0
|
property double fontScale: 1.0
|
||||||
@@ -36,10 +35,6 @@ Page {
|
|||||||
text: "Indice capitoli"
|
text: "Indice capitoli"
|
||||||
onClicked: showChapterList()
|
onClicked: showChapterList()
|
||||||
}
|
}
|
||||||
MenuItem {
|
|
||||||
text: "Aggiorna posizione sul server"
|
|
||||||
onClicked: syncNow()
|
|
||||||
}
|
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: "Margini del testo"
|
text: "Margini del testo"
|
||||||
onClicked: {
|
onClicked: {
|
||||||
@@ -201,20 +196,11 @@ Page {
|
|||||||
if (bookId <= 0)
|
if (bookId <= 0)
|
||||||
return
|
return
|
||||||
page.saveCounter++
|
page.saveCounter++
|
||||||
// salva locale ogni ~10 s, sync server ogni ~30 s
|
// salva in locale ogni ~10 s (o subito su forzatura)
|
||||||
if (force || page.saveCounter % 5 === 0) {
|
if (force || page.saveCounter % 5 === 0) {
|
||||||
appSettings.setBookmarkKey(page.bookId, page.bookFormat,
|
appSettings.setBookmarkKey(page.bookId, page.bookFormat,
|
||||||
book.makeBookmarkKey(page.currentChapter, page.chapterFraction))
|
book.makeBookmarkKey(page.currentChapter, page.chapterFraction))
|
||||||
}
|
}
|
||||||
if (force || page.syncCounter++ % 15 === 0)
|
|
||||||
syncNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncNow() {
|
|
||||||
if (bookId <= 0)
|
|
||||||
return
|
|
||||||
apiClient.postBookmark(page.bookId, page.bookFormat,
|
|
||||||
book.makeBookmarkKey(page.currentChapter, page.chapterFraction))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function gotoChapter(index) {
|
function gotoChapter(index) {
|
||||||
@@ -315,11 +301,6 @@ Page {
|
|||||||
page.currentChapter = ch
|
page.currentChapter = ch
|
||||||
page.chapterFraction = fr
|
page.chapterFraction = fr
|
||||||
loadChapter()
|
loadChapter()
|
||||||
|
|
||||||
// ripristino anche dal server: la posizione remota vince, se esiste.
|
|
||||||
// Il login di sessione viene fatto qui (prima richiesta verso le route web)
|
|
||||||
if (page.bookId > 0)
|
|
||||||
apiClient.fetchBookmark(page.bookId, page.bookFormat)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onStatusChanged: {
|
onStatusChanged: {
|
||||||
@@ -332,33 +313,4 @@ Page {
|
|||||||
applyMarginsJS()
|
applyMarginsJS()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: apiClient
|
|
||||||
|
|
||||||
// posizione letta dal server all'apertura del libro
|
|
||||||
onBookmarkReady: {
|
|
||||||
if (bookId !== page.bookId)
|
|
||||||
return
|
|
||||||
if (key.length === 0)
|
|
||||||
return // nessun segnalibro sul server: resta la posizione locale
|
|
||||||
var r = book.parseBookmarkKey(key)
|
|
||||||
if (!r || !r.hasOwnProperty("chapter"))
|
|
||||||
return
|
|
||||||
var same = (r.chapter === page.currentChapter)
|
|
||||||
&& (Math.abs(r.fraction - page.chapterFraction) < 0.01)
|
|
||||||
if (same)
|
|
||||||
return
|
|
||||||
page.currentChapter = r.chapter
|
|
||||||
page.chapterFraction = r.fraction
|
|
||||||
// aggiorna anche la copia locale
|
|
||||||
appSettings.setBookmarkKey(page.bookId, page.bookFormat, key)
|
|
||||||
if (page.ready && book.chapterCount() > 0)
|
|
||||||
loadChapter()
|
|
||||||
}
|
|
||||||
// sync non disponibile (server irraggiungibile, login bloccato, ...):
|
|
||||||
// si continua in locale, senza disturbare il lettore
|
|
||||||
onBookmarkError: { }
|
|
||||||
onLoginFailed: { }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,12 @@
|
|||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
|
||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
#include <QDateTime>
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
#include <QRegularExpression>
|
|
||||||
#include <QSslError>
|
#include <QSslError>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
@@ -136,250 +134,6 @@ static bool looksLikeImage(const QByteArray &data)
|
|||||||
|| data.startsWith("GIF8") || data.startsWith("RIFF");
|
|| data.startsWith("GIF8") || data.startsWith("RIFF");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Login di sessione calibre-web (route web: richiedono flask-login, il Basic
|
|
||||||
// auth serve solo per OPDS)
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
QString ApiClient::extractCsrfToken(const QByteArray &body) const
|
|
||||||
{
|
|
||||||
// <input id="csrf_token" name="csrf_token" type="hidden" value="...">
|
|
||||||
QRegularExpression re(QStringLiteral(
|
|
||||||
"name=[\"']csrf_token[\"'][^>]*value=[\"']([^\"']+)[\"']"));
|
|
||||||
const QRegularExpressionMatch match = re.match(QString::fromUtf8(body));
|
|
||||||
return match.hasMatch() ? match.captured(1) : QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiClient::ensureLogin(const std::function<void()> &after)
|
|
||||||
{
|
|
||||||
if (m_loggedIn) {
|
|
||||||
after();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (m_loginInProgress) {
|
|
||||||
m_loginQueue.append(after);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// accesso anonimo: nessun login possibile, esegui comunque (l'endpoint
|
|
||||||
// deciderà); oppure cooldown dopo un login fallito (limiter server 3/min)
|
|
||||||
if (m_settings->username().isEmpty()
|
|
||||||
|| QDateTime::currentMSecsSinceEpoch() < m_loginCooldownUntil) {
|
|
||||||
after();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_loginInProgress = true;
|
|
||||||
m_loginQueue.append(after);
|
|
||||||
|
|
||||||
// 1. GET /login per il token CSRF
|
|
||||||
const QUrl loginUrl(m_settings->baseUrl() + QStringLiteral("/login"));
|
|
||||||
QNetworkRequest request(loginUrl);
|
|
||||||
request.setRawHeader("User-Agent", "harbour-calibreweb/0.1");
|
|
||||||
QNetworkReply *reply = m_nam.get(request);
|
|
||||||
if (m_settings->ignoreSslErrors()) {
|
|
||||||
connect(reply, &QNetworkReply::sslErrors, reply,
|
|
||||||
static_cast<void (QNetworkReply::*)(const QList<QSslError> &)>(
|
|
||||||
&QNetworkReply::ignoreSslErrors));
|
|
||||||
}
|
|
||||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
|
||||||
reply->deleteLater();
|
|
||||||
const QString token = extractCsrfToken(reply->readAll());
|
|
||||||
if (token.isEmpty()) {
|
|
||||||
qDebug() << "login: token CSRF non trovato";
|
|
||||||
finishLogin(false, QStringLiteral(
|
|
||||||
"Login non disponibile: pagina di login non raggiungibile"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. POST del form standard calibre-web
|
|
||||||
QNetworkRequest req(QUrl(m_settings->baseUrl() + QStringLiteral("/login")));
|
|
||||||
req.setRawHeader("User-Agent", "harbour-calibreweb/0.1");
|
|
||||||
req.setHeader(QNetworkRequest::ContentTypeHeader,
|
|
||||||
QLatin1String("application/x-www-form-urlencoded"));
|
|
||||||
applyAuth(req);
|
|
||||||
const QByteArray body = "username=" + QUrl::toPercentEncoding(m_settings->username())
|
|
||||||
+ "&password=" + QUrl::toPercentEncoding(m_settings->password())
|
|
||||||
+ "&csrf_token=" + QUrl::toPercentEncoding(token)
|
|
||||||
+ "&next=/&remember_me=on&submit=Submit";
|
|
||||||
QNetworkReply *post = m_nam.post(req, body);
|
|
||||||
if (m_settings->ignoreSslErrors()) {
|
|
||||||
connect(post, &QNetworkReply::sslErrors, post,
|
|
||||||
static_cast<void (QNetworkReply::*)(const QList<QSslError> &)>(
|
|
||||||
&QNetworkReply::ignoreSslErrors));
|
|
||||||
}
|
|
||||||
connect(post, &QNetworkReply::finished, this, [this, post]() {
|
|
||||||
post->deleteLater();
|
|
||||||
const int status = post->attribute(
|
|
||||||
QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
||||||
const QByteArray respBody = post->readAll();
|
|
||||||
// successo: redirect a / (QNAM segue) e il body non contiene il
|
|
||||||
// form di login; fallimento: la pagina di login viene ri-renderizzata
|
|
||||||
const bool stillForm = respBody.contains("name=\"password\"")
|
|
||||||
|| respBody.contains("name='password'");
|
|
||||||
if (status >= 400 || stillForm) {
|
|
||||||
qDebug() << "login fallito status:" << status
|
|
||||||
<< "form-ancora-presente=" << stillForm;
|
|
||||||
finishLogin(false, QStringLiteral(
|
|
||||||
"Login fallito: credenziali non valide o accesso bloccato"));
|
|
||||||
} else {
|
|
||||||
qDebug() << "login OK status:" << status;
|
|
||||||
finishLogin(true, QString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiClient::finishLogin(bool ok, const QString &message)
|
|
||||||
{
|
|
||||||
m_loginInProgress = false;
|
|
||||||
m_loggedIn = ok;
|
|
||||||
if (!ok)
|
|
||||||
m_loginCooldownUntil = QDateTime::currentMSecsSinceEpoch() + 60000;
|
|
||||||
const QList<std::function<void()>> queue = m_loginQueue;
|
|
||||||
m_loginQueue.clear();
|
|
||||||
if (!ok && !message.isEmpty())
|
|
||||||
emit loginFailed(message);
|
|
||||||
// esegui la coda anche in caso di fallimento: le richieste andranno a
|
|
||||||
// vuoto in modo rilevato (bookmarkError), senza bloccare il lettore
|
|
||||||
for (const auto &fn : queue)
|
|
||||||
fn();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiClient::postBookmark(int bookId, const QString &format, const QString &key)
|
|
||||||
{
|
|
||||||
ensureLogin([this, bookId, format, key]() {
|
|
||||||
sendBookmark(bookId, format, key);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiClient::sendBookmark(int bookId, const QString &format, const QString &key)
|
|
||||||
{
|
|
||||||
const QString url = m_settings->baseUrl() + QStringLiteral("/ajax/bookmark/")
|
|
||||||
+ QString::number(bookId) + QLatin1Char('/') + format;
|
|
||||||
qDebug() << "postBookmark:" << url << "key=" << key;
|
|
||||||
|
|
||||||
const QUrl requestUrl(url);
|
|
||||||
QNetworkRequest request(requestUrl);
|
|
||||||
request.setRawHeader("User-Agent", "harbour-calibreweb/0.1");
|
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader,
|
|
||||||
QLatin1String("application/x-www-form-urlencoded"));
|
|
||||||
applyAuth(request);
|
|
||||||
const QByteArray body = "bookmark=" + QUrl::toPercentEncoding(key);
|
|
||||||
|
|
||||||
QNetworkReply *reply = m_nam.post(request, body);
|
|
||||||
if (m_settings->ignoreSslErrors()) {
|
|
||||||
connect(reply, &QNetworkReply::sslErrors, reply,
|
|
||||||
static_cast<void (QNetworkReply::*)(const QList<QSslError> &)>(
|
|
||||||
&QNetworkReply::ignoreSslErrors));
|
|
||||||
}
|
|
||||||
connect(reply, &QNetworkReply::finished, this, [this, reply, url, key, bookId]() {
|
|
||||||
reply->deleteLater();
|
|
||||||
const int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
||||||
const QByteArray respBody = reply->readAll();
|
|
||||||
qDebug() << "postBookmark status:" << status;
|
|
||||||
// senza sessione il server risponde col form di login (302 seguito)
|
|
||||||
const bool authFail = respBody.contains("name=\"password\"")
|
|
||||||
|| respBody.contains("name='password'");
|
|
||||||
if (authFail) {
|
|
||||||
qDebug() << "postBookmark: sessione non valida (form di login nella risposta)";
|
|
||||||
emit bookmarkError(bookId, QStringLiteral("Sync segnalibro: sessione non valida"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 400/403 = probabile CSRF attivo: recupera il token dalla pagina di login e riprova
|
|
||||||
if ((status == 400 || status == 403) && !m_csrfRetried) {
|
|
||||||
m_csrfRetried = true;
|
|
||||||
fetchCsrfTokenAndRetry(url, key);
|
|
||||||
} else if (status >= 400) {
|
|
||||||
qDebug() << "postBookmark ERROR status:" << status;
|
|
||||||
emit bookmarkError(bookId, QStringLiteral("Sync segnalibro fallita (%1)").arg(status));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiClient::fetchCsrfTokenAndRetry(const QString &url, const QString &key)
|
|
||||||
{
|
|
||||||
const QUrl loginUrl(m_settings->baseUrl() + QStringLiteral("/login"));
|
|
||||||
QNetworkRequest request(loginUrl);
|
|
||||||
request.setRawHeader("User-Agent", "harbour-calibreweb/0.1");
|
|
||||||
applyAuth(request);
|
|
||||||
QNetworkReply *reply = m_nam.get(request);
|
|
||||||
if (m_settings->ignoreSslErrors()) {
|
|
||||||
connect(reply, &QNetworkReply::sslErrors, reply,
|
|
||||||
static_cast<void (QNetworkReply::*)(const QList<QSslError> &)>(
|
|
||||||
&QNetworkReply::ignoreSslErrors));
|
|
||||||
}
|
|
||||||
connect(reply, &QNetworkReply::finished, this, [this, reply, url, key]() {
|
|
||||||
reply->deleteLater();
|
|
||||||
const QString csrfToken = extractCsrfToken(reply->readAll());
|
|
||||||
if (!csrfToken.isEmpty()) {
|
|
||||||
postBookmarkWithCsrf(url, key, csrfToken);
|
|
||||||
} else {
|
|
||||||
qDebug() << "postBookmark: token CSRF non trovato nella pagina di login";
|
|
||||||
}
|
|
||||||
m_csrfRetried = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiClient::postBookmarkWithCsrf(const QString &url, const QString &key,
|
|
||||||
const QString &csrfToken)
|
|
||||||
{
|
|
||||||
const QUrl requestUrl(url);
|
|
||||||
QNetworkRequest request(requestUrl);
|
|
||||||
request.setRawHeader("User-Agent", "harbour-calibreweb/0.1");
|
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader,
|
|
||||||
QLatin1String("application/x-www-form-urlencoded"));
|
|
||||||
request.setRawHeader("X-CSRFToken", csrfToken.toUtf8());
|
|
||||||
applyAuth(request);
|
|
||||||
const QByteArray body = "bookmark=" + QUrl::toPercentEncoding(key);
|
|
||||||
QNetworkReply *reply = m_nam.post(request, body);
|
|
||||||
if (m_settings->ignoreSslErrors()) {
|
|
||||||
connect(reply, &QNetworkReply::sslErrors, reply,
|
|
||||||
static_cast<void (QNetworkReply::*)(const QList<QSslError> &)>(
|
|
||||||
&QNetworkReply::ignoreSslErrors));
|
|
||||||
}
|
|
||||||
connect(reply, &QNetworkReply::finished, this, [reply]() {
|
|
||||||
reply->deleteLater();
|
|
||||||
const int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
||||||
qDebug() << "postBookmark (con CSRF) status:" << status;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiClient::fetchBookmark(int bookId, const QString &format)
|
|
||||||
{
|
|
||||||
if (bookId <= 0)
|
|
||||||
return;
|
|
||||||
ensureLogin([this, bookId, format]() {
|
|
||||||
requestBookmark(bookId, format);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiClient::requestBookmark(int bookId, const QString &format)
|
|
||||||
{
|
|
||||||
const QString url = m_settings->baseUrl() + QStringLiteral("/read/")
|
|
||||||
+ QString::number(bookId) + QLatin1Char('/') + format;
|
|
||||||
qDebug() << "fetchBookmark:" << url;
|
|
||||||
|
|
||||||
QNetworkReply *reply = startGet(QUrl(url));
|
|
||||||
connect(reply, &QNetworkReply::finished, this, [this, reply, bookId, format]() {
|
|
||||||
reply->deleteLater();
|
|
||||||
const int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
||||||
if (reply->error() != QNetworkReply::NoError || status >= 400) {
|
|
||||||
qDebug() << "fetchBookmark ERROR status:" << status
|
|
||||||
<< "err:" << reply->errorString();
|
|
||||||
emit bookmarkError(bookId, QStringLiteral(
|
|
||||||
"Segnalibro server non disponibile (%1)").arg(status));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const QByteArray body = reply->readAll();
|
|
||||||
// l'HTML del reader incorpora la posizione: bookmark: "chiave"
|
|
||||||
QRegularExpression re(QStringLiteral("bookmark:\\s*\"([^\"]*)\""));
|
|
||||||
const QRegularExpressionMatch m = re.match(QString::fromUtf8(body));
|
|
||||||
const QString key = m.hasMatch() ? m.captured(1).trimmed() : QString();
|
|
||||||
qDebug() << "fetchBookmark OK book:" << bookId << "key=[" << key << "]";
|
|
||||||
emit bookmarkReady(bookId, format, key);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiClient::fetchImage(const QString &url, const QString &id)
|
void ApiClient::fetchImage(const QString &url, const QString &id)
|
||||||
{
|
{
|
||||||
if (url.isEmpty()) {
|
if (url.isEmpty()) {
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QVariantList>
|
#include <QVariantList>
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
class QNetworkReply;
|
class QNetworkReply;
|
||||||
class QNetworkRequest;
|
class QNetworkRequest;
|
||||||
class Settings;
|
class Settings;
|
||||||
@@ -14,9 +12,6 @@ class Settings;
|
|||||||
// Cliente OPDS: scarica i feed (getFeed), li parsa e li espone come QVariantList
|
// Cliente OPDS: scarica i feed (getFeed), li parsa e li espone come QVariantList
|
||||||
// di entry (title, authors, summary, coverUrl, formats, isNavigation, linkUrl).
|
// di entry (title, authors, summary, coverUrl, formats, isNavigation, linkUrl).
|
||||||
// Gestisce anche il fetch delle copertine in una cache locale su disco.
|
// Gestisce anche il fetch delle copertine in una cache locale su disco.
|
||||||
// Le route web di calibre-web (login, /read, /ajax/bookmark) richiedono una
|
|
||||||
// sessione flask-login: l'app fa login col form standard (username+password+
|
|
||||||
// csrf_token) e riusa il cookie di sessione del QNetworkAccessManager.
|
|
||||||
class ApiClient : public QObject
|
class ApiClient : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -26,44 +21,20 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE void getFeed(const QString &url);
|
Q_INVOKABLE void getFeed(const QString &url);
|
||||||
Q_INVOKABLE void fetchImage(const QString &url, const QString &id);
|
Q_INVOKABLE void fetchImage(const QString &url, const QString &id);
|
||||||
// sincronizza la posizione di lettura su calibre-web (POST /ajax/bookmark)
|
|
||||||
Q_INVOKABLE void postBookmark(int bookId, const QString &format, const QString &key);
|
|
||||||
// legge il segnalibro dal server: GET /read/<id>/<format> e estrae
|
|
||||||
// bookmark: "chiave" dall'HTML del reader
|
|
||||||
Q_INVOKABLE void fetchBookmark(int bookId, const QString &format);
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void feedReady(const QVariantList &entries, const QString &nextUrl, const QString &feedTitle);
|
void feedReady(const QVariantList &entries, const QString &nextUrl, const QString &feedTitle);
|
||||||
void feedError(const QString &message);
|
void feedError(const QString &message);
|
||||||
void imageReady(const QString &id, const QString &localPath);
|
void imageReady(const QString &id, const QString &localPath);
|
||||||
// chiave vuota = nessun segnalibro sul server
|
|
||||||
void bookmarkReady(int bookId, const QString &format, const QString &key);
|
|
||||||
void bookmarkError(int bookId, const QString &message);
|
|
||||||
void loginFailed(const QString &message);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QNetworkReply *startGet(const QUrl &url);
|
QNetworkReply *startGet(const QUrl &url);
|
||||||
void applyAuth(QNetworkRequest &request);
|
void applyAuth(QNetworkRequest &request);
|
||||||
QString resolveUrl(const QString &relative) const;
|
QString resolveUrl(const QString &relative) const;
|
||||||
// login di sessione: esegue after() subito se già autenticati, altrimenti
|
|
||||||
// fa login e poi esegue (coda per le richieste arrivate nel frattempo)
|
|
||||||
void ensureLogin(const std::function<void()> &after);
|
|
||||||
void finishLogin(bool ok, const QString &message);
|
|
||||||
QString extractCsrfToken(const QByteArray &body) const;
|
|
||||||
void sendBookmark(int bookId, const QString &format, const QString &key);
|
|
||||||
void postBookmarkWithCsrf(const QString &url, const QString &key,
|
|
||||||
const QString &csrfToken);
|
|
||||||
void fetchCsrfTokenAndRetry(const QString &url, const QString &key);
|
|
||||||
void requestBookmark(int bookId, const QString &format);
|
|
||||||
|
|
||||||
Settings *m_settings;
|
Settings *m_settings;
|
||||||
QNetworkAccessManager m_nam;
|
QNetworkAccessManager m_nam;
|
||||||
QString m_cacheDir;
|
QString m_cacheDir;
|
||||||
bool m_csrfRetried = false;
|
|
||||||
bool m_loggedIn = false;
|
|
||||||
bool m_loginInProgress = false;
|
|
||||||
qint64 m_loginCooldownUntil = 0; // ms epoch: niente retry login per 60 s dopo un fallimento
|
|
||||||
QList<std::function<void()>> m_loginQueue;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // APICLIENT_H
|
#endif // APICLIENT_H
|
||||||
|
|||||||
Reference in New Issue
Block a user