-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathusagereportingmanager.h
More file actions
191 lines (158 loc) · 8.55 KB
/
Copy pathusagereportingmanager.h
File metadata and controls
191 lines (158 loc) · 8.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
#pragma once
#include <QMetaType>
#include <QObject>
#include <QSet>
#include <QString>
#include <QUrl>
#include <functional>
class QNetworkAccessManager;
class QNetworkReply;
// ─────────────────────────────────────────────────────────────
// Usage reporting
// ─────────────────────────────────────────────────────────────
// Reports coarse "an install started up" statistics to the NaturalPoint usage
// backend so active-user counts can be tracked. Two reports exist:
//
// POST /startup once per run, as soon as the app is up
// POST /clickmode whenever the user switches how clicks are triggered
//
// This is the TrackClick port of TrackIR's UsageReportingManager. Two
// deliberate differences from that original:
// * it is built on Qt (QNetworkAccessManager + QJsonDocument) rather than
// httplib/nlohmann, which TrackClick does not depend on — the requests are
// driven by the event loop instead of a worker thread;
// * TrackIR's /camera report has no analogue here (there is no camera), so
// the second report carries the clicking mode instead.
//
// Nothing user-identifying is collected: the session id is a random UUID minted
// per run and never persisted, and the user id is a salted one-way hash whose
// salt never leaves the machine (see userid.h).
// How clicks are triggered. Reported as the "clickingMode" metric — this is
// the TrackClick-specific statistic the backend does not get from TrackIR.
enum class ClickingMode {
Dwell = 0, // cursor held still for the dwell period
Audio = 1 // a loud sound through the microphone
};
struct StartupReportPayload {
QString operatingSystem;
QString softwareType;
QString softwareVersion;
int statusId = 0; // last connected client id (unused here)
int trackingMode = 0; // TrackIR parity — always 0 for TrackClick
int trackingClip = 0; // TrackIR parity — always 0 for TrackClick
ClickingMode clickingMode = ClickingMode::Dwell;
};
struct ClickModeReportPayload {
QString softwareType;
ClickingMode clickingMode = ClickingMode::Dwell;
};
// The backend's stock startup reply. Most of it describes TrackIR downloads
// and is not acted on here; it is parsed and kept so the fields are available
// if TrackClick ever grows an update check.
struct StartupReportResponse {
QString latestSoftwareVersion;
QString softwareDownloadUrl;
bool newerVersionAvailable = false;
QString minimumSoftwareVersion;
QString latestGameslistDownloadUrl;
int latestGameslistVersion = 0;
QString newsUrl;
int newsVersion = 0;
};
struct ClickModeReportResponse {
bool success = false;
};
Q_DECLARE_METATYPE(StartupReportResponse)
Q_DECLARE_METATYPE(ClickModeReportResponse)
class UsageReportingManager : public QObject
{
Q_OBJECT
public:
explicit UsageReportingManager(QObject* parent = nullptr);
~UsageReportingManager() override;
// Base URL of the reporting server; the report paths are appended to it.
// Any path component of the URL is kept as a prefix.
void setServerEndpoint(const QString& url) { m_serverUrl = url; }
QString serverEndpoint() const { return m_serverUrl; }
void setStartupPayload(const StartupReportPayload& payload) { m_startupPayload = payload; }
// Stable per-install identifier, reported with /startup so launches can be
// grouped into returning installs rather than only counted. Must be a
// salted hash — see userid.h for why an unsalted one would not be anonymous.
// Left empty when unset, in which case the field is omitted from the report.
void setUserId(const QString& id) { m_userId = id; }
const QString& userId() const { return m_userId; }
// Master switch for all outbound reporting (the user-facing opt-out). When
// off, every send path — sendStartupReport(), sendClickModeReport(),
// shutdown() and the destructor's safety net — becomes a no-op and no
// request is made at all, so opting out means no network traffic rather
// than a report carrying an "opted out" flag. The gate lives here rather
// than at the call sites because shutdown() sends whatever has not been
// reported yet, and would otherwise flush both reports on quit regardless.
// On by default; MainWindow applies the persisted choice before the first
// report and again whenever the setting changes.
void setReportingEnabled(bool on) { m_reportingEnabled = on; }
bool reportingEnabled() const { return m_reportingEnabled; }
// Both are asynchronous: they return immediately and emit the matching
// *Finished signal once the reply arrives (or the request fails).
void sendStartupReport();
void sendClickModeReport(const ClickModeReportPayload& payload);
// Waits (briefly) for any in-flight request, then sends whatever has not
// been reported yet so a short session still gets counted. Idempotent —
// safe to call from both aboutToQuit and the destructor.
void shutdown();
const QString& sessionId() const { return m_sessionId; }
const StartupReportPayload& startupPayload() const { return m_startupPayload; }
const StartupReportResponse& lastStartupResponse() const { return m_lastStartupResponse; }
const ClickModeReportResponse& lastClickModeResponse() const { return m_lastClickModeResponse; }
bool startupReportSent() const { return m_startupReportSent; }
bool clickModeReportSent() const { return m_clickModeReportSent; }
signals:
void startupReportFinished(bool success, const StartupReportResponse& response);
void clickModeReportFinished(bool success, const ClickModeReportResponse& response);
private:
// A run-scoped random UUID, used only to tie the two reports together.
static QString generateSessionId();
// Builds the absolute URL for a report path. Returns an invalid QUrl when
// the configured endpoint cannot be parsed.
QUrl urlFor(const QString& path) const;
// Issues the POST and arms its abort timer. Returns nullptr (having logged)
// when the endpoint is unusable.
QNetworkReply* startPost(const QString& path, const QByteArray& body, int timeoutMs);
// Fires off a POST and hands the outcome to `done`. `done` is always
// called exactly once, including when the request could not be started.
void post(const QString& path, const QByteArray& body, std::function<void(bool)> done);
// Same request, but blocks on a nested event loop for at most
// kShutdownTimeoutMs. Only used from shutdown().
bool postBlocking(const QString& path, const QByteArray& body);
// Bounded wait for in-flight requests; anything still running when the
// budget runs out is aborted. Only used from shutdown().
void waitForPending();
// Shared reply handling: status check, logging, response parsing.
bool handleReply(const QString& path, QNetworkReply* reply);
bool parseStartupResponse(const QByteArray& json, StartupReportResponse& out);
bool parseClickModeResponse(const QByteArray& json, ClickModeReportResponse& out);
QByteArray serializeStartupPayload() const;
QByteArray serializeClickModePayload(const ClickModeReportPayload& payload) const;
static constexpr const char* kDefaultServerUrl = "https://aznexp01.planar.com/";
static constexpr const char* kStartupPath = "/startup";
static constexpr const char* kClickModePath = "/clickmode";
static constexpr int kRequestTimeoutMs = 10000;
// Deliberately shorter than the normal timeout: this one runs while the user
// is waiting for the window to disappear.
static constexpr int kShutdownTimeoutMs = 3000;
QNetworkAccessManager* m_nam = nullptr;
QString m_sessionId;
QString m_userId;
QString m_serverUrl;
StartupReportPayload m_startupPayload;
StartupReportResponse m_lastStartupResponse;
ClickModeReportResponse m_lastClickModeResponse;
// Mode carried by the last /clickmode report we sent (or tried to send), so
// the shutdown catch-up reports the mode actually in use.
ClickingMode m_lastClickMode = ClickingMode::Dwell;
QSet<QNetworkReply*> m_pending;
bool m_startupReportSent = false;
bool m_clickModeReportSent = false;
bool m_shuttingDown = false;
bool m_reportingEnabled = true;
};