Files
status-app/ui/StatusQ/include/StatusQ/networkaccessfactory.h
Andrey Bocharnikov fee211d783 feat(StatusQ): account for the QML layer's HTTP traffic and cache it to disk
The QML engine gets a network access factory that hands out managers with a
disk cache and per-host accounting, so every image, animation and XHR the QML
layer issues can be seen and capped.

HttpStats is a GUI-thread singleton recording per-host totals split by whether
the bytes came from the network or from the cache. Bytes are taken from
downloadProgress rather than Content-Length, which is absent on some responses
and wrong on chunked ones. It is constructed on the GUI thread before any
factory worker can first-touch it with the wrong QObject affinity, and it
reports a finished cache clear on its own cacheCleared() signal — separate from
changed(), which fires on every reply and so cannot be what a reader hangs an
expensive re-measurement on.

The cache takes an explicit budget instead of Qt's 50 MB default, which a
single collectibles page can evict on its own.

The factory also sends Accept: image/webp,image/png,image/jpeg on Cloudinary
delivery URLs, so the f_auto in the rewritten media URLs has something to
resolve against. The list is narrow on purpose: f_auto returns AVIF or JPEG XL
to a client that claims them and Qt decodes neither. It is a statement about
this client's decoders, which is why it lives here and not in QML — see
docs/adr/0006-qt-http-cache.md.

All of it lives in networkaccessfactory.{h,cpp} rather than in externc.cpp,
which exists to be a thin C-ABI shim for Nim: the shim now casts the engine
pointer and delegates. That also lets Storybook install the same factory, so
media traffic measured on a Storybook page is the traffic the application
produces — and it lets the Accept policy be tested, which as a static function
in a .cpp with no header it could not be.

TestHttpStats and TestNetworkAccessFactory cover the thread affinity, both
clear paths, and the Accept policy as a table.
2026-08-06 14:26:30 +04:00

60 lines
2.0 KiB
C++

#pragma once
#include <QNetworkRequest>
#include <QQmlNetworkAccessManagerFactory>
#include <QString>
#include <QtGlobal>
class QNetworkAccessManager;
class QQmlEngine;
namespace Status {
/*!
* Returns \a request with an \c Accept header claiming the image formats Qt can
* actually decode, or unchanged when the header does not apply.
*
* Cloudinary's \c f_auto picks a format from \c Accept; Qt image loading sends
* none, so without this the delivery hints written by \c Utils.resizedMediaSource
* come back as PNG (see docs/adr/0006-qt-http-cache.md). The list is
* narrow on purpose: \c f_auto would otherwise return AVIF or JPEG XL, which Qt
* decodes neither of. Scoped to the two Cloudinary delivery paths that carry those
* hints — the same manager also serves status-go, XHR and everything else the QML
* layer fetches.
*
* A request that already carries an \c Accept header is returned untouched: the
* caller stated its own terms and they win.
*/
QNetworkRequest withAcceptedImageFormats(const QNetworkRequest& request);
/*!
* Hands the QML engine network access managers that cache to disk and report
* every reply to \c HttpStats.
*
* Every manager the factory creates shares one cache directory, so \a maxCacheSize
* is the budget for the whole QML layer rather than a per-manager allowance.
*/
class NetworkAccessFactory : public QQmlNetworkAccessManagerFactory
{
public:
NetworkAccessFactory(const QString& cacheDir, qint64 maxCacheSize);
QNetworkAccessManager* create(QObject* parent) override;
private:
QString m_cacheDir;
qint64 m_maxCacheSize;
};
/*!
* Installs a \c NetworkAccessFactory on \a engine. Call from the GUI thread,
* before the engine loads anything.
*
* The factory must outlive the engine and is intentionally never deleted
* (process lifetime, as in DOtherSide).
*/
void setupNetworkAccessManagerFactory(QQmlEngine* engine, const QString& cacheDir,
qint64 maxCacheSize);
} // namespace Status