├── .gitignore ├── LICENSE.txt ├── README.md ├── examples ├── console-app │ ├── console-app.pro │ └── main.cpp ├── examples.pro └── qtquick-app │ ├── MainWindow.qml │ ├── main.cpp │ ├── qtquick-app.pro │ └── resource.qrc ├── ganalytics.cpp ├── ganalytics.h ├── qt-google-analytics.pri └── qt-google-analytics.pro /.gitignore: -------------------------------------------------------------------------------- 1 | build-* 2 | *.pro.* 3 | 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2015, University of Applied Sciences Augsburg 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the University of Applied Sciences Augsburg nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 19 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 21 | OODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | UT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | qt-google-analytics 2 | ================ 3 | 4 | Qt5 classes for providing google analytics usage in a Qt/QML application. 5 | 6 | ## Building 7 | Include ```qt-google-analytics.pri``` in your .pro file. 8 | 9 | ## Using 10 | Please make sure you have set your application information using ```QApplication::setApplicationName``` and ```QApplication::setApplicationVersion```. 11 | 12 | ### In C++: 13 | ``` 14 | GAnalytics tracker("UA-my-id"); 15 | tracker.sendScreenView("Main Screen"); 16 | ``` 17 | 18 | ### In QtQuick: 19 | Register the class on the C++ side using ```qmlRegisterType("analytics", 0, 1, "Tracker");``` 20 | ``` 21 | Tracker { 22 | id: tracker 23 | trackingID: "UA-my-id" 24 | } 25 | 26 | [...] 27 | tracker.sendScreenView("Main Screen") 28 | ``` 29 | 30 | There is also an example application in the examples folder. 31 | 32 | ## License 33 | Copyright (c) 2014-2019, University of Applied Sciences Augsburg. 34 | All rights reserved. Distributed under the terms and conditions of the BSD License. See separate LICENSE.txt. 35 | -------------------------------------------------------------------------------- /examples/console-app/console-app.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | QT = core network 3 | 4 | CONFIG += console 5 | CONFIG -= app_bundle 6 | 7 | SOURCES += main.cpp 8 | 9 | include(../../qt-google-analytics.pri) 10 | -------------------------------------------------------------------------------- /examples/console-app/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "ganalytics.h" 5 | 6 | class Watcher : public QObject 7 | { 8 | Q_OBJECT 9 | 10 | public slots: 11 | void onIsSendingChanged(bool sending) 12 | { 13 | if (sending) 14 | return; 15 | 16 | QCoreApplication::instance()->quit(); 17 | } 18 | }; 19 | 20 | int main(int argc, char* argv[]) 21 | { 22 | QCoreApplication::setOrganizationName("HSAnet"); 23 | QCoreApplication::setApplicationName("Console-App"); 24 | QCoreApplication::setApplicationVersion("0.1"); 25 | 26 | QCoreApplication app(argc, argv); 27 | 28 | // Create the tracker 29 | GAnalytics tracker("UA-53395376-1"); 30 | 31 | Watcher watcher; 32 | QObject::connect(&tracker, SIGNAL(isSendingChanged(bool)), &watcher, SLOT(onIsSendingChanged(bool))); 33 | 34 | // Shorten the interval 35 | tracker.setSendInterval(5 * 1000); 36 | 37 | // Send some dummy events 38 | tracker.sendEvent("lifecycle", "application_started"); 39 | tracker.sendEvent("lifecycle", "application_stopped"); 40 | 41 | qDebug() << "Waiting until the evens were sent ..."; 42 | 43 | int ret = app.exec(); 44 | 45 | qDebug() << "Shutting down."; 46 | 47 | return ret; 48 | } 49 | 50 | #include "main.moc" 51 | -------------------------------------------------------------------------------- /examples/examples.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += \ 4 | console-app \ 5 | qtquick-app 6 | -------------------------------------------------------------------------------- /examples/qtquick-app/MainWindow.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.2 3 | import QtQuick.Layouts 1.1 4 | import QtQuick.Dialogs 1.1 5 | import analytics 0.1 6 | 7 | ApplicationWindow { 8 | id: root 9 | title: "QtQuick App" 10 | 11 | width: mainLayout.implicitWidth + 2 * margin 12 | height: mainLayout.implicitWidth + 2 * margin 13 | minimumWidth: mainLayout.Layout.minimumWidth + 2 * margin 14 | minimumHeight: mainLayout.Layout.minimumHeight + 2 * margin 15 | visible: true 16 | 17 | // TODO: Please change this id to yours 18 | property string defaultTrackingId: "UA-53395376-1" 19 | property int margin: 11 20 | 21 | Component.onDestruction: { 22 | tracker.endSession() 23 | } 24 | 25 | Tracker { 26 | id: tracker 27 | logLevel: Tracker.Debug 28 | sendInterval: 20*1000 29 | viewportSize: qsTr("%1x%2").arg(root.width).arg(root.height) 30 | trackingID: defaultTrackingId 31 | } 32 | 33 | menuBar: MenuBar { 34 | Menu { 35 | title: "&File" 36 | MenuItem { 37 | text: "&Quit" 38 | shortcut: "CTRL+Q" 39 | onTriggered: Qt.quit() 40 | } 41 | } 42 | } 43 | 44 | statusBar: StatusBar { 45 | RowLayout { 46 | Label { 47 | text: "Sending data ..." 48 | visible: tracker.isSending 49 | } 50 | } 51 | } 52 | 53 | ColumnLayout { 54 | id: mainLayout 55 | anchors.fill: parent 56 | anchors.margins: margin 57 | 58 | GroupBox { 59 | Layout.fillWidth: true 60 | title: "General information" 61 | 62 | GridLayout { 63 | id: rowLayout 64 | anchors.fill: parent 65 | anchors.margins: margin 66 | columns: 2 67 | 68 | Label { text: "Tracking ID" } 69 | TextField { 70 | Layout.fillWidth: true 71 | text: tracker.trackingID 72 | onTextChanged: tracker.trackingID = text 73 | } 74 | 75 | Label { text: "Viewport Size" } 76 | TextField { 77 | Layout.fillWidth: true 78 | readOnly: true 79 | text: tracker.viewportSize 80 | } 81 | 82 | 83 | Label { text: "Language" } 84 | TextField { 85 | Layout.fillWidth: true 86 | readOnly: true 87 | text: tracker.language 88 | } 89 | 90 | 91 | Label { text: "Send Interval" } 92 | TextField { 93 | Layout.fillWidth: true 94 | readOnly: true 95 | text: tracker.sendInterval 96 | } 97 | } 98 | } 99 | 100 | GroupBox { 101 | Layout.fillWidth: true 102 | title: "Fun box" 103 | 104 | ColumnLayout { 105 | anchors.fill: parent 106 | 107 | RowLayout { 108 | Layout.fillWidth: true 109 | 110 | Button { 111 | text: "Trigger event" 112 | onClicked: tracker.sendEvent("ui_event", "button_press", text) 113 | } 114 | 115 | Button { 116 | text: "Trigger exception" 117 | onClicked: tracker.sendException("some exception", "whatever") 118 | } 119 | } 120 | 121 | RowLayout { 122 | Layout.fillWidth: true 123 | 124 | Button { 125 | text: "Change current screen name to:" 126 | onClicked: tracker.sendScreenView(screenName.text) 127 | } 128 | 129 | TextField { 130 | id: screenName 131 | Layout.fillWidth: true 132 | text: "MainWindow" 133 | } 134 | } 135 | } 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /examples/qtquick-app/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "ganalytics.h" 8 | 9 | int main(int argc, char* argv[]) 10 | { 11 | QApplication::setApplicationName("QtQuick-App"); 12 | QApplication::setApplicationVersion("0.1"); 13 | 14 | QApplication app(argc, argv); 15 | 16 | qmlRegisterType("analytics", 0, 1, "Tracker"); 17 | 18 | QQmlApplicationEngine engine(QUrl("qrc:/qml/MainWindow.qml")); 19 | 20 | return app.exec(); 21 | } 22 | -------------------------------------------------------------------------------- /examples/qtquick-app/qtquick-app.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | QT += quick qml widgets network 3 | 4 | SOURCES += main.cpp 5 | 6 | include(../../qt-google-analytics.pri) 7 | 8 | OTHER_FILES += \ 9 | MainWindow.qml 10 | 11 | RESOURCES += \ 12 | resource.qrc 13 | -------------------------------------------------------------------------------- /examples/qtquick-app/resource.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow.qml 4 | 5 | 6 | -------------------------------------------------------------------------------- /ganalytics.cpp: -------------------------------------------------------------------------------- 1 | #include "ganalytics.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #ifdef QT_GUI_LIB 18 | #include 19 | #include 20 | #endif // QT_GUI_LIB 21 | 22 | #ifdef QT_QML_LIB 23 | #include 24 | #include 25 | #endif // QT_QML_LIB 26 | 27 | struct QueryBuffer 28 | { 29 | QUrlQuery postQuery; 30 | QDateTime time; 31 | }; 32 | 33 | /** 34 | * Class Private 35 | * Private members and functions. 36 | */ 37 | class GAnalytics::Private : public QObject 38 | { 39 | Q_OBJECT 40 | 41 | public: 42 | explicit Private(GAnalytics *parent = 0); 43 | ~Private(); 44 | 45 | GAnalytics *q; 46 | 47 | QNetworkAccessManager *networkManager; 48 | 49 | QQueue messageQueue; 50 | QTimer timer; 51 | QNetworkRequest request; 52 | GAnalytics::LogLevel logLevel; 53 | 54 | QString trackingID; 55 | QString clientID; 56 | QString userID; 57 | QString appName; 58 | QString appVersion; 59 | QString language; 60 | QString screenResolution; 61 | QString viewportSize; 62 | 63 | bool isSending; 64 | 65 | const static int fourHours = 4 * 60 * 60 * 1000; 66 | const static QString dateTimeFormat; 67 | 68 | public: 69 | void logMessage(GAnalytics::LogLevel level, const QString &message); 70 | 71 | QUrlQuery buildStandardPostQuery(const QString &type); 72 | #ifdef QT_GUI_LIB 73 | QString getScreenResolution(); 74 | #endif // QT_GUI_LIB 75 | QString getUserAgent(); 76 | QString getSystemInfo(); 77 | QList persistMessageQueue(); 78 | void readMessagesFromFile(const QList &dataList); 79 | QString getClientID(); 80 | QString getUserID(); 81 | void setUserID(const QString &userID); 82 | void enqueQueryWithCurrentTime(const QUrlQuery &query); 83 | void setIsSending(bool doSend); 84 | 85 | signals: 86 | void postNextMessage(); 87 | 88 | public slots: 89 | void postMessage(); 90 | void postMessageFinished(); 91 | }; 92 | 93 | const QString GAnalytics::Private::dateTimeFormat = "yyyy,MM,dd-hh:mm::ss:zzz"; 94 | 95 | /** 96 | * Constructor 97 | * Constructs an object of class Private. 98 | * @param parent 99 | */ 100 | GAnalytics::Private::Private(GAnalytics *parent) 101 | : QObject(parent) 102 | , q(parent) 103 | , networkManager(NULL) 104 | , request(QUrl("http://www.google-analytics.com/collect")) 105 | , logLevel(GAnalytics::Error) 106 | , isSending(false) 107 | { 108 | clientID = getClientID(); 109 | userID = getUserID(); 110 | language = QLocale::system().name().toLower().replace("_", "-"); 111 | #ifdef QT_GUI_LIB 112 | screenResolution = getScreenResolution(); 113 | #endif // QT_GUI_LIB 114 | request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); 115 | appName = QCoreApplication::instance()->applicationName(); 116 | appVersion = QCoreApplication::instance()->applicationVersion(); 117 | request.setHeader(QNetworkRequest::UserAgentHeader, getUserAgent()); 118 | connect(this, SIGNAL(postNextMessage()), this, SLOT(postMessage())); 119 | timer.start(30000); 120 | connect(&timer, SIGNAL(timeout()), this, SLOT(postMessage())); 121 | } 122 | 123 | /** 124 | * Destructor 125 | * Delete an object of class Private. 126 | */ 127 | GAnalytics::Private::~Private() 128 | { 129 | } 130 | 131 | void GAnalytics::Private::logMessage(LogLevel level, const QString &message) 132 | { 133 | if (logLevel > level) 134 | { 135 | return; 136 | } 137 | 138 | qDebug() << "[Analytics]" << message; 139 | } 140 | 141 | /** 142 | * Build the POST query. Adds all parameter to the query 143 | * which are used in every POST. 144 | * @param type Type of POST message. The event which is to post. 145 | * @return query Most used parameter in a query for a POST. 146 | */ 147 | QUrlQuery GAnalytics::Private::buildStandardPostQuery(const QString &type) 148 | { 149 | QUrlQuery query; 150 | query.addQueryItem("v", "1"); 151 | query.addQueryItem("tid", trackingID); 152 | query.addQueryItem("cid", clientID); 153 | if(!userID.isEmpty()) 154 | { 155 | query.addQueryItem("uid", userID); 156 | } 157 | query.addQueryItem("t", type); 158 | query.addQueryItem("ul", language); 159 | 160 | #ifdef QT_GUI_LIB 161 | query.addQueryItem("vp", viewportSize); 162 | query.addQueryItem("sr", screenResolution); 163 | #endif // QT_GUI_LIB 164 | 165 | return query; 166 | } 167 | 168 | #ifdef QT_GUI_LIB 169 | /** 170 | * Get devicese screen resolution. 171 | * @return A QString like "800x600". 172 | */ 173 | QString GAnalytics::Private::getScreenResolution() 174 | { 175 | QScreen *screen = QGuiApplication::primaryScreen(); 176 | QSize size = screen->size(); 177 | 178 | return QString("%1x%2").arg(size.width()).arg(size.height()); 179 | } 180 | #endif // QT_GUI_LIB 181 | 182 | 183 | /** 184 | * Try to gain information about the system where this application 185 | * is running. It needs to get the name and version of the operating 186 | * system, the language and screen resolution. 187 | * All this information will be send in POST messages. 188 | * @return agent A QString with all the information formatted for a POST message. 189 | */ 190 | QString GAnalytics::Private::getUserAgent() 191 | { 192 | QString locale = QLocale::system().name(); 193 | QString system = getSystemInfo(); 194 | 195 | return QString("%1/%2 (%3; %4) GAnalytics/1.0 (Qt/%5)").arg(appName).arg(appVersion).arg(system).arg(locale).arg(QT_VERSION_STR); 196 | } 197 | 198 | 199 | #ifdef Q_OS_MAC 200 | /** 201 | * Only on Mac OS X 202 | * Get the Operating system name and version. 203 | * @return os The operating system name and version in a string. 204 | */ 205 | QString GAnalytics::Private::getSystemInfo() 206 | { 207 | QSysInfo::MacVersion version = QSysInfo::macVersion(); 208 | QString os; 209 | switch (version) 210 | { 211 | case QSysInfo::MV_9: 212 | os = "Macintosh; Mac OS 9"; 213 | break; 214 | case QSysInfo::MV_10_0: 215 | os = "Macintosh; Mac OS 10.0"; 216 | break; 217 | case QSysInfo::MV_10_1: 218 | os = "Macintosh; Mac OS 10.1"; 219 | break; 220 | case QSysInfo::MV_10_2: 221 | os = "Macintosh; Mac OS 10.2"; 222 | break; 223 | case QSysInfo::MV_10_3: 224 | os = "Macintosh; Mac OS 10.3"; 225 | break; 226 | case QSysInfo::MV_10_4: 227 | os = "Macintosh; Mac OS 10.4"; 228 | break; 229 | case QSysInfo::MV_10_5: 230 | os = "Macintosh; Mac OS 10.5"; 231 | break; 232 | case QSysInfo::MV_10_6: 233 | os = "Macintosh; Mac OS 10.6"; 234 | break; 235 | case QSysInfo::MV_10_7: 236 | os = "Macintosh; Mac OS 10.7"; 237 | break; 238 | case QSysInfo::MV_10_8: 239 | os = "Macintosh; Mac OS 10.8"; 240 | break; 241 | case QSysInfo::MV_10_9: 242 | os = "Macintosh; Mac OS 10.9"; 243 | break; 244 | case QSysInfo::MV_10_10: 245 | os = "Macintosh; Mac OS 10.10"; 246 | break; 247 | case QSysInfo::MV_10_11: 248 | os = "Macintosh; Mac OS 10.11"; 249 | break; 250 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) 251 | case QSysInfo::MV_10_12: 252 | os = "Macintosh; Mac OS 10.12"; 253 | break; 254 | #endif 255 | case QSysInfo::MV_Unknown: 256 | os = "Macintosh; Mac OS unknown"; 257 | break; 258 | case QSysInfo::MV_IOS_5_0: 259 | os = "iPhone; iOS 5.0"; 260 | break; 261 | case QSysInfo::MV_IOS_5_1: 262 | os = "iPhone; iOS 5.1"; 263 | break; 264 | case QSysInfo::MV_IOS_6_0: 265 | os = "iPhone; iOS 6.0"; 266 | break; 267 | case QSysInfo::MV_IOS_6_1: 268 | os = "iPhone; iOS 6.1"; 269 | break; 270 | case QSysInfo::MV_IOS_7_0: 271 | os = "iPhone; iOS 7.0"; 272 | break; 273 | case QSysInfo::MV_IOS_7_1: 274 | os = "iPhone; iOS 7.1"; 275 | break; 276 | case QSysInfo::MV_IOS_8_0: 277 | os = "iPhone; iOS 8.0"; 278 | break; 279 | case QSysInfo::MV_IOS_8_1: 280 | os = "iPhone; iOS 8.1"; 281 | break; 282 | case QSysInfo::MV_IOS_8_2: 283 | os = "iPhone; iOS 8.2"; 284 | break; 285 | case QSysInfo::MV_IOS_8_3: 286 | os = "iPhone; iOS 8.3"; 287 | break; 288 | case QSysInfo::MV_IOS_8_4: 289 | os = "iPhone; iOS 8.4"; 290 | break; 291 | case QSysInfo::MV_IOS_9_0: 292 | os = "iPhone; iOS 9.0"; 293 | break; 294 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) 295 | case QSysInfo::MV_IOS_9_1: 296 | os = "iPhone; iOS 9.1"; 297 | break; 298 | case QSysInfo::MV_IOS_9_2: 299 | os = "iPhone; iOS 9.2"; 300 | break; 301 | case QSysInfo::MV_IOS_9_3: 302 | os = "iPhone; iOS 9.3"; 303 | break; 304 | case QSysInfo::MV_IOS_10_0: 305 | os = "iPhone; iOS 10.0"; 306 | break; 307 | #endif 308 | case QSysInfo::MV_IOS: 309 | os = "iPhone; iOS unknown"; 310 | break; 311 | default: 312 | os = "Macintosh"; 313 | break; 314 | } 315 | return os; 316 | } 317 | #endif 318 | 319 | #ifdef Q_OS_WIN 320 | /** 321 | * Only on Windows 322 | * Get operating system and its version. 323 | * @return os A QString containing the oprating systems name and version. 324 | */ 325 | QString GAnalytics::Private::getSystemInfo() 326 | { 327 | QSysInfo::WinVersion version = QSysInfo::windowsVersion(); 328 | QString os("Windows; "); 329 | switch (version) 330 | { 331 | case QSysInfo::WV_95: 332 | os += "Win 95"; 333 | break; 334 | case QSysInfo::WV_98: 335 | os += "Win 98"; 336 | break; 337 | case QSysInfo::WV_Me: 338 | os += "Win ME"; 339 | break; 340 | case QSysInfo::WV_NT: 341 | os += "Win NT"; 342 | break; 343 | case QSysInfo::WV_2000: 344 | os += "Win 2000"; 345 | break; 346 | case QSysInfo::WV_2003: 347 | os += "Win Server 2003"; 348 | break; 349 | case QSysInfo::WV_VISTA: 350 | os += "Win Vista"; 351 | break; 352 | case QSysInfo::WV_WINDOWS7: 353 | os += "Win 7"; 354 | break; 355 | case QSysInfo::WV_WINDOWS8: 356 | os += "Win 8"; 357 | break; 358 | case QSysInfo::WV_WINDOWS8_1: 359 | os += "Win 8.1"; 360 | break; 361 | case QSysInfo::WV_WINDOWS10: 362 | os += "Win 10"; 363 | break; 364 | default: 365 | os = "Windows; unknown"; 366 | break; 367 | } 368 | return os; 369 | } 370 | #endif 371 | 372 | #if defined(Q_OS_ANDROID) 373 | #include 374 | 375 | QString GAnalytics::Private::getSystemInfo() 376 | { 377 | return QString("Linux; U; Android %1; %2 %3 Build/%4; %5") 378 | .arg(QAndroidJniObject::getStaticObjectField("android/os/Build$VERSION", "RELEASE").toString()) 379 | .arg(QAndroidJniObject::getStaticObjectField("android/os/Build", "MANUFACTURER").toString()) 380 | .arg(QAndroidJniObject::getStaticObjectField("android/os/Build", "MODEL").toString()) 381 | .arg(QAndroidJniObject::getStaticObjectField("android/os/Build", "ID").toString()) 382 | .arg(QAndroidJniObject::getStaticObjectField("android/os/Build", "BRAND").toString()); 383 | } 384 | #elif defined(Q_OS_LINUX) 385 | #include 386 | 387 | /** 388 | * Only on Unix systems. 389 | * Get operation system name and version. 390 | * @return os A QString with the name and version of the operating system. 391 | */ 392 | QString GAnalytics::Private::getSystemInfo() 393 | { 394 | struct utsname buf; 395 | uname(&buf); 396 | QString system(buf.sysname); 397 | QString release(buf.release); 398 | 399 | return system + "; " + release; 400 | } 401 | #endif 402 | 403 | 404 | /** 405 | * The message queue contains a list of QueryBuffer object. 406 | * QueryBuffer holds a QUrlQuery object and a QDateTime object. 407 | * These both object are freed from the buffer object and 408 | * inserted as QString objects in a QList. 409 | * @return dataList The list with concartinated queue data. 410 | */ 411 | QList GAnalytics::Private::persistMessageQueue() 412 | { 413 | QList dataList; 414 | foreach (QueryBuffer buffer, messageQueue) 415 | { 416 | dataList << buffer.postQuery.toString(); 417 | dataList << buffer.time.toString(dateTimeFormat); 418 | } 419 | 420 | return dataList; 421 | } 422 | 423 | /** 424 | * Reads persistent messages from a file. 425 | * Gets all message data as a QList. 426 | * Two lines in the list build a QueryBuffer object. 427 | */ 428 | void GAnalytics::Private::readMessagesFromFile(const QList &dataList) 429 | { 430 | QListIterator iter(dataList); 431 | while (iter.hasNext()) 432 | { 433 | QString queryString = iter.next(); 434 | if(!iter.hasNext()) 435 | break; 436 | QString dateString = iter.next(); 437 | if(queryString.isEmpty() || dateString.isEmpty()) 438 | break; 439 | QUrlQuery query; 440 | query.setQuery(queryString); 441 | QDateTime dateTime = QDateTime::fromString(dateString, dateTimeFormat); 442 | QueryBuffer buffer; 443 | buffer.postQuery = query; 444 | buffer.time = dateTime; 445 | messageQueue.enqueue(buffer); 446 | } 447 | } 448 | 449 | /** 450 | * Change the user id. 451 | * @param userID A string with the user id. 452 | */ 453 | void GAnalytics::Private::setUserID(const QString &userID) 454 | { 455 | this->userID = userID; 456 | QSettings settings; 457 | settings.setValue("GAnalytics-uid", userID); 458 | } 459 | 460 | /** 461 | * Get the user id. 462 | * User id once created is stored in application settings. 463 | * @return userID A string with the user id. 464 | */ 465 | QString GAnalytics::Private::getUserID() 466 | { 467 | QSettings settings; 468 | QString userID = settings.value("GAnalytics-uid", QString("")).toString(); 469 | 470 | return userID; 471 | } 472 | 473 | /** 474 | * Get the client id. 475 | * Client id once created is stored in application settings. 476 | * @return clientID A string with the client id. 477 | */ 478 | QString GAnalytics::Private::getClientID() 479 | { 480 | QSettings settings; 481 | QString clientID; 482 | if (!settings.contains("GAnalytics-cid")) 483 | { 484 | clientID = QUuid::createUuid().toString(); 485 | settings.setValue("GAnalytics-cid", clientID); 486 | } 487 | else 488 | { 489 | clientID = settings.value("GAnalytics-cid").toString(); 490 | } 491 | 492 | return clientID; 493 | } 494 | 495 | /** 496 | * Takes a QUrlQuery object and wrapp it together with 497 | * a QTime object into a QueryBuffer struct. These struct 498 | * will be stored in the message queue. 499 | * @param query 500 | */ 501 | void GAnalytics::Private::enqueQueryWithCurrentTime(const QUrlQuery &query) 502 | { 503 | QueryBuffer buffer; 504 | buffer.postQuery = query; 505 | buffer.time = QDateTime::currentDateTime(); 506 | 507 | messageQueue.enqueue(buffer); 508 | } 509 | 510 | /** 511 | * Change status of class. Emit signal that status was changed. 512 | * @param doSend 513 | */ 514 | void GAnalytics::Private::setIsSending(bool doSend) 515 | { 516 | if (doSend) 517 | { 518 | timer.stop(); 519 | } 520 | else 521 | { 522 | timer.start(); 523 | } 524 | 525 | bool changed = (isSending != doSend); 526 | 527 | isSending = doSend; 528 | 529 | if (changed) 530 | { 531 | emit q->isSendingChanged(isSending); 532 | } 533 | } 534 | 535 | 536 | /** 537 | * CONSTRUCTOR GAnalytics 538 | * ------------------------------------------------------------------------------------------------------------ 539 | * Constructs the GAnalytics Object. 540 | * @param parent The application which uses this object. 541 | * @param trackingID 542 | * @param clientID 543 | * @param withGet Determines wheather the messages are send with GET or POST. 544 | */ 545 | GAnalytics::GAnalytics(QObject *parent) 546 | : QObject(parent) 547 | , d(new Private(this)) 548 | { 549 | } 550 | 551 | GAnalytics::GAnalytics(const QString &trackingID, QObject *parent) 552 | : QObject(parent) 553 | , d(new Private(this)) 554 | { 555 | setTrackingID(trackingID); 556 | } 557 | 558 | /** 559 | * Destructor of class GAnalytics. 560 | */ 561 | GAnalytics::~GAnalytics() 562 | { 563 | delete d; 564 | } 565 | 566 | void GAnalytics::setLogLevel(GAnalytics::LogLevel logLevel) 567 | { 568 | if (d->logLevel != logLevel) 569 | { 570 | d->logLevel = logLevel; 571 | emit logLevelChanged(); 572 | } 573 | } 574 | 575 | GAnalytics::LogLevel GAnalytics::logLevel() const 576 | { 577 | return d->logLevel; 578 | } 579 | 580 | // SETTER and GETTER 581 | void GAnalytics::setViewportSize(const QString &viewportSize) 582 | { 583 | if (d->viewportSize != viewportSize) 584 | { 585 | d->viewportSize = viewportSize; 586 | emit viewportSizeChanged(); 587 | } 588 | } 589 | 590 | QString GAnalytics::viewportSize() const 591 | { 592 | return d->viewportSize; 593 | } 594 | 595 | void GAnalytics::setLanguage(const QString &language) 596 | { 597 | if (d->language != language) 598 | { 599 | d->language = language; 600 | emit languageChanged(); 601 | } 602 | } 603 | 604 | QString GAnalytics::language() const 605 | { 606 | return d->language; 607 | } 608 | 609 | void GAnalytics::setTrackingID(const QString &trackingID) 610 | { 611 | if (d->trackingID != trackingID) 612 | { 613 | d->trackingID = trackingID; 614 | emit trackingIDChanged(); 615 | } 616 | } 617 | 618 | QString GAnalytics::trackingID() const 619 | { 620 | return d->trackingID; 621 | } 622 | 623 | void GAnalytics::setSendInterval(int milliseconds) 624 | { 625 | if (d->timer.interval() != milliseconds) 626 | { 627 | d->timer.setInterval(milliseconds); 628 | emit sendIntervalChanged(); 629 | } 630 | } 631 | 632 | void GAnalytics::setUserID(const QString &userID) 633 | { 634 | if(d->userID != userID) 635 | { 636 | d->setUserID(userID); 637 | emit userIDChanged(); 638 | } 639 | } 640 | 641 | QString GAnalytics::userID() const 642 | { 643 | return d->getUserID(); 644 | } 645 | 646 | int GAnalytics::sendInterval() const 647 | { 648 | return (d->timer.interval()); 649 | } 650 | 651 | void GAnalytics::startSending() 652 | { 653 | if (!isSending()) 654 | emit d->postNextMessage(); 655 | } 656 | 657 | bool GAnalytics::isSending() const 658 | { 659 | return d->isSending; 660 | } 661 | 662 | void GAnalytics::setNetworkAccessManager(QNetworkAccessManager *networkAccessManager) 663 | { 664 | if (d->networkManager != networkAccessManager) 665 | { 666 | // Delete the old network manager if it was our child 667 | if (d->networkManager && d->networkManager->parent() == this) 668 | { 669 | d->networkManager->deleteLater(); 670 | } 671 | 672 | d->networkManager = networkAccessManager; 673 | } 674 | } 675 | 676 | QNetworkAccessManager *GAnalytics::networkAccessManager() const 677 | { 678 | return d->networkManager; 679 | } 680 | 681 | static void appendCustomValues(QUrlQuery &query, const QVariantMap &customValues) { 682 | for(QVariantMap::const_iterator iter = customValues.begin(); iter != customValues.end(); ++iter) { 683 | query.addQueryItem(iter.key(), iter.value().toString()); 684 | } 685 | } 686 | 687 | 688 | /** 689 | * SentAppview is called when the user changed the applications view. 690 | * Deprecated because after SDK Version 3.08 and up no more "appview" event: 691 | * Use sendScreenView() instead 692 | * @param appName 693 | * @param appVersion 694 | * @param screenName 695 | */ 696 | void GAnalytics::sendAppView(const QString &screenName, 697 | const QVariantMap &customValues) 698 | { 699 | sendScreenView(screenName, customValues); 700 | } 701 | 702 | /** 703 | * Sent screen view is called when the user changed the applications view. 704 | * These action of the user should be noticed and reported. Therefore 705 | * a QUrlQuery is build in this method. It holts all the parameter for 706 | * a http POST. The UrlQuery will be stored in a message Queue. 707 | * @param appName 708 | * @param appVersion 709 | * @param screenName 710 | */ 711 | void GAnalytics::sendScreenView(const QString &screenName, 712 | const QVariantMap &customValues) 713 | { 714 | d->logMessage(Info, QString("ScreenView: %1").arg(screenName)); 715 | 716 | QUrlQuery query = d->buildStandardPostQuery("screenview"); 717 | query.addQueryItem("cd", screenName); 718 | query.addQueryItem("an", d->appName); 719 | query.addQueryItem("av", d->appVersion); 720 | appendCustomValues(query, customValues); 721 | 722 | d->enqueQueryWithCurrentTime(query); 723 | } 724 | 725 | /** 726 | * This method is called whenever a button was pressed in the application. 727 | * A query for a POST message will be created to report this event. The 728 | * created query will be stored in a message queue. 729 | * @param eventCategory 730 | * @param eventAction 731 | * @param eventLabel 732 | * @param eventValue 733 | */ 734 | void GAnalytics::sendEvent(const QString &category, const QString &action, 735 | const QString &label, const QVariant &value, 736 | const QVariantMap &customValues) 737 | { 738 | QUrlQuery query = d->buildStandardPostQuery("event"); 739 | query.addQueryItem("an", d->appName); 740 | query.addQueryItem("av", d->appVersion); 741 | query.addQueryItem("ec", category); 742 | query.addQueryItem("ea", action); 743 | if (! label.isEmpty()) 744 | query.addQueryItem("el", label); 745 | if (value.isValid()) 746 | query.addQueryItem("ev", value.toString()); 747 | 748 | appendCustomValues(query, customValues); 749 | 750 | d->enqueQueryWithCurrentTime(query); 751 | } 752 | 753 | /** 754 | * Method is called after an exception was raised. It builds a 755 | * query for a POST message. These query will be stored in a 756 | * message queue. 757 | * @param exceptionDescription 758 | * @param exceptionFatal 759 | */ 760 | void GAnalytics::sendException(const QString &exceptionDescription, 761 | bool exceptionFatal, 762 | const QVariantMap &customValues) 763 | { 764 | QUrlQuery query = d->buildStandardPostQuery("exception"); 765 | query.addQueryItem("an", d->appName); 766 | query.addQueryItem("av", d->appVersion); 767 | 768 | query.addQueryItem("exd", exceptionDescription); 769 | 770 | if (exceptionFatal) 771 | { 772 | query.addQueryItem("exf", "1"); 773 | } 774 | else 775 | { 776 | query.addQueryItem("exf", "0"); 777 | } 778 | appendCustomValues(query, customValues); 779 | 780 | d->enqueQueryWithCurrentTime(query); 781 | } 782 | 783 | /** 784 | * Session starts. This event will be sent by a POST message. 785 | * Query is setup in this method and stored in the message 786 | * queue. 787 | */ 788 | void GAnalytics::startSession() 789 | { 790 | QVariantMap customValues; 791 | customValues.insert("sc", "start"); 792 | sendEvent("Session", "Start", QString(), QVariant(), customValues); 793 | } 794 | 795 | /** 796 | * Session ends. This event will be sent by a POST message. 797 | * Query is setup in this method and stored in the message 798 | * queue. 799 | */ 800 | void GAnalytics::endSession() 801 | { 802 | QVariantMap customValues; 803 | customValues.insert("sc", "end"); 804 | sendEvent("Session", "End", QString(), QVariant(), customValues); 805 | } 806 | 807 | /** 808 | * This function is called by a timer interval. 809 | * The function tries to send a messages from the queue. 810 | * If message was successfully send then this function 811 | * will be called back to send next message. 812 | * If message queue contains more than one message then 813 | * the connection will kept open. 814 | * The message POST is asyncroniously when the server 815 | * answered a signal will be emitted. 816 | */ 817 | void GAnalytics::Private::postMessage() 818 | { 819 | if (messageQueue.isEmpty()) 820 | { 821 | setIsSending(false); 822 | return; 823 | } 824 | else 825 | { 826 | setIsSending(true); 827 | } 828 | 829 | QString connection = "close"; 830 | if (messageQueue.count() > 1) 831 | { 832 | connection = "keep-alive"; 833 | } 834 | 835 | QueryBuffer buffer = messageQueue.head(); 836 | QDateTime sendTime = QDateTime::currentDateTime(); 837 | qint64 timeDiff = buffer.time.msecsTo(sendTime); 838 | 839 | if(timeDiff > fourHours) 840 | { 841 | // too old. 842 | messageQueue.dequeue(); 843 | emit postNextMessage(); 844 | return; 845 | } 846 | 847 | buffer.postQuery.addQueryItem("qt", QString::number(timeDiff)); 848 | request.setRawHeader("Connection", connection.toUtf8()); 849 | QByteArray ba; 850 | ba = buffer.postQuery.query(QUrl::FullyEncoded).toUtf8(); 851 | request.setHeader(QNetworkRequest::ContentLengthHeader, ba.length()); 852 | 853 | // Create a new network access manager if we don't have one yet 854 | if (networkManager == NULL) 855 | { 856 | networkManager = new QNetworkAccessManager(this); 857 | } 858 | 859 | QNetworkReply *reply = networkManager->post(request, ba); 860 | connect(reply, SIGNAL(finished()), this, SLOT(postMessageFinished())); 861 | } 862 | 863 | /** 864 | * NetworkAccsessManager has finished to POST a message. 865 | * If POST message was successfully send then the message 866 | * query should be removed from queue. 867 | * SIGNAL "postMessage" will be emitted to send next message 868 | * if there is any. 869 | * If message couldn't be send then next try is when the 870 | * timer emits its signal. 871 | */ 872 | void GAnalytics::Private::postMessageFinished() 873 | { 874 | QNetworkReply *reply = qobject_cast(sender()); 875 | reply->deleteLater(); 876 | 877 | int httpStausCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); 878 | if (httpStausCode < 200 || httpStausCode > 299) 879 | { 880 | logMessage(GAnalytics::Error, QString("Error posting message: %1").arg(reply->errorString())); 881 | 882 | // An error ocurred. 883 | setIsSending(false); 884 | return; 885 | } 886 | else 887 | { 888 | logMessage(GAnalytics::Debug, "Message sent"); 889 | } 890 | 891 | messageQueue.dequeue(); 892 | emit postNextMessage(); 893 | } 894 | 895 | 896 | /** 897 | * Qut stream to persist class GAnalytics. 898 | * @param outStream 899 | * @param analytics 900 | * @return 901 | */ 902 | QDataStream &operator<<(QDataStream &outStream, const GAnalytics &analytics) 903 | { 904 | outStream << analytics.d->persistMessageQueue(); 905 | 906 | return outStream; 907 | } 908 | 909 | 910 | /** 911 | * In stream to read GAnalytics from file. 912 | * @param inStream 913 | * @param analytics 914 | * @return 915 | */ 916 | QDataStream &operator >>(QDataStream &inStream, GAnalytics &analytics) 917 | { 918 | QList dataList; 919 | inStream >> dataList; 920 | analytics.d->readMessagesFromFile(dataList); 921 | 922 | return inStream; 923 | } 924 | 925 | #ifdef QT_QML_LIB 926 | void GAnalytics::classBegin() 927 | { 928 | // Get the network access manager from the QmlEngine 929 | QQmlContext *context = QQmlEngine::contextForObject(this); 930 | if (context) 931 | { 932 | QQmlEngine *engine = context->engine(); 933 | setNetworkAccessManager(engine->networkAccessManager()); 934 | } 935 | } 936 | 937 | void GAnalytics::componentComplete() 938 | { 939 | } 940 | #endif // QT_QML_LIB 941 | 942 | #include "ganalytics.moc" 943 | -------------------------------------------------------------------------------- /ganalytics.h: -------------------------------------------------------------------------------- 1 | #ifndef GANALYTICS_H 2 | #define GANALYTICS_H 3 | 4 | #include 5 | #include 6 | 7 | #ifdef QT_QML_LIB 8 | #include 9 | #endif // QT_QML_LIB 10 | 11 | class QNetworkAccessManager; 12 | 13 | class GAnalytics : public QObject 14 | #ifdef QT_QML_LIB 15 | , public QQmlParserStatus 16 | #endif // QT_QML_LIB 17 | { 18 | Q_OBJECT 19 | #ifdef QT_QML_LIB 20 | Q_INTERFACES(QQmlParserStatus) 21 | #endif // QT_QML_LIB 22 | Q_ENUMS(LogLevel) 23 | Q_PROPERTY(LogLevel logLevel READ logLevel WRITE setLogLevel NOTIFY logLevelChanged) 24 | Q_PROPERTY(QString viewportSize READ viewportSize WRITE setViewportSize NOTIFY viewportSizeChanged) 25 | Q_PROPERTY(QString language READ language WRITE setLanguage NOTIFY languageChanged) 26 | Q_PROPERTY(QString trackingID READ trackingID WRITE setTrackingID NOTIFY trackingIDChanged) 27 | Q_PROPERTY(QString userID READ userID WRITE setUserID NOTIFY userIDChanged) 28 | Q_PROPERTY(int sendInterval READ sendInterval WRITE setSendInterval NOTIFY sendIntervalChanged) 29 | Q_PROPERTY(bool isSending READ isSending NOTIFY isSendingChanged) 30 | 31 | public: 32 | explicit GAnalytics(QObject *parent = 0); 33 | explicit GAnalytics(const QString &trackingID, QObject *parent = 0); 34 | ~GAnalytics(); 35 | 36 | public: 37 | enum LogLevel 38 | { 39 | Debug, 40 | Info, 41 | Error, 42 | None 43 | }; 44 | 45 | void setLogLevel(LogLevel logLevel); 46 | LogLevel logLevel() const; 47 | 48 | // Getter and Setters 49 | void setViewportSize(const QString &viewportSize); 50 | QString viewportSize() const; 51 | 52 | void setLanguage(const QString &language); 53 | QString language() const; 54 | 55 | void setTrackingID(const QString &trackingID); 56 | QString trackingID() const; 57 | 58 | void setUserID(const QString &userID); 59 | QString userID() const; 60 | 61 | void setSendInterval(int milliseconds); 62 | int sendInterval() const; 63 | 64 | void startSending(); 65 | bool isSending() const; 66 | 67 | /// Get or set the network access manager. If none is set, the class creates its own on the first request 68 | void setNetworkAccessManager(QNetworkAccessManager *networkAccessManager); 69 | QNetworkAccessManager *networkAccessManager() const; 70 | 71 | #ifdef QT_QML_LIB 72 | // QQmlParserStatus interface 73 | void classBegin(); 74 | void componentComplete(); 75 | #endif // QT_QML_LIB 76 | 77 | public slots: 78 | void sendScreenView(const QString &screenName, 79 | const QVariantMap &customValues = QVariantMap()); 80 | void sendAppView(const QString &screenName, 81 | const QVariantMap &customValues = QVariantMap()); 82 | void sendEvent(const QString &category, 83 | const QString &action, 84 | const QString &label = QString(), 85 | const QVariant &value = QVariant(), 86 | const QVariantMap &customValues = QVariantMap()); 87 | void sendException(const QString &exceptionDescription, 88 | bool exceptionFatal = true, 89 | const QVariantMap &customValues = QVariantMap()); 90 | void startSession(); 91 | void endSession(); 92 | 93 | 94 | signals: 95 | void logLevelChanged(); 96 | void viewportSizeChanged(); 97 | void languageChanged(); 98 | void trackingIDChanged(); 99 | void userIDChanged(); 100 | void sendIntervalChanged(); 101 | void isSendingChanged(bool isSending); 102 | 103 | private: 104 | class Private; 105 | Private *d; 106 | 107 | friend QDataStream& operator<<(QDataStream &outStream, const GAnalytics &analytics); 108 | friend QDataStream& operator>>(QDataStream &inStream, GAnalytics &analytics); 109 | }; 110 | 111 | QDataStream& operator<<(QDataStream &outStream, const GAnalytics &analytics); 112 | QDataStream& operator>>(QDataStream &inStream, GAnalytics &analytics); 113 | 114 | #endif // GANALYTICS_H 115 | -------------------------------------------------------------------------------- /qt-google-analytics.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD 2 | HEADERS += $$PWD/ganalytics.h 3 | SOURCES += $$PWD/ganalytics.cpp 4 | -------------------------------------------------------------------------------- /qt-google-analytics.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | CONFIG += staticlib 3 | 4 | QT += network 5 | 6 | include(qt-google-analytics.pri) 7 | 8 | OTHER_FILES += qt-google-analytics.pri 9 | --------------------------------------------------------------------------------