From 2f8c752d1fe9976fdbd683d34ae3dcbf4e797591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 20 Nov 2016 12:32:27 +0100 Subject: NOISSUE reformat and sanitize ganalytics --- libraries/ganalytics/src/ganalytics.cpp | 222 ++++++++++++++++++++++ libraries/ganalytics/src/ganalytics_worker.cpp | 252 +++++++++++++++++++++++++ libraries/ganalytics/src/ganalytics_worker.h | 61 ++++++ libraries/ganalytics/src/sys.h | 11 ++ libraries/ganalytics/src/sys_apple.cpp | 114 +++++++++++ libraries/ganalytics/src/sys_unix.cpp | 13 ++ libraries/ganalytics/src/sys_win32.cpp | 50 +++++ 7 files changed, 723 insertions(+) create mode 100644 libraries/ganalytics/src/ganalytics.cpp create mode 100644 libraries/ganalytics/src/ganalytics_worker.cpp create mode 100644 libraries/ganalytics/src/ganalytics_worker.h create mode 100644 libraries/ganalytics/src/sys.h create mode 100644 libraries/ganalytics/src/sys_apple.cpp create mode 100644 libraries/ganalytics/src/sys_unix.cpp create mode 100644 libraries/ganalytics/src/sys_win32.cpp (limited to 'libraries/ganalytics/src') diff --git a/libraries/ganalytics/src/ganalytics.cpp b/libraries/ganalytics/src/ganalytics.cpp new file mode 100644 index 00000000..8d25efe9 --- /dev/null +++ b/libraries/ganalytics/src/ganalytics.cpp @@ -0,0 +1,222 @@ +#include "ganalytics.h" +#include "ganalytics_worker.h" +#include "sys.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +GAnalytics::GAnalytics(const QString &trackingID, const QString &clientID, QObject *parent) : QObject(parent) +{ + d = new GAnalyticsWorker(this); + d->m_trackingID = trackingID; + d->m_clientID = clientID; +} + +/** + * Destructor of class GAnalytics. + */ +GAnalytics::~GAnalytics() +{ + delete d; +} + +void GAnalytics::setLogLevel(GAnalytics::LogLevel logLevel) +{ + d->m_logLevel = logLevel; +} + +GAnalytics::LogLevel GAnalytics::logLevel() const +{ + return d->m_logLevel; +} + +// SETTER and GETTER +void GAnalytics::setViewportSize(const QString &viewportSize) +{ + d->m_viewportSize = viewportSize; +} + +QString GAnalytics::viewportSize() const +{ + return d->m_viewportSize; +} + +void GAnalytics::setLanguage(const QString &language) +{ + d->m_language = language; +} + +QString GAnalytics::language() const +{ + return d->m_language; +} + +void GAnalytics::setSendInterval(int milliseconds) +{ + d->m_timer.setInterval(milliseconds); +} + +int GAnalytics::sendInterval() const +{ + return (d->m_timer.interval()); +} + +void GAnalytics::startSending() +{ + if (!isSending()) + d->postMessage(); +} + +bool GAnalytics::isSending() const +{ + return d->m_isSending; +} + +void GAnalytics::setNetworkAccessManager(QNetworkAccessManager *networkAccessManager) +{ + if (d->networkManager != networkAccessManager) + { + // Delete the old network manager if it was our child + if (d->networkManager && d->networkManager->parent() == this) + { + d->networkManager->deleteLater(); + } + + d->networkManager = networkAccessManager; + } +} + +QNetworkAccessManager *GAnalytics::networkAccessManager() const +{ + return d->networkManager; +} + +static void appendCustomValues(QUrlQuery &query, const QVariantMap &customValues) +{ + for (QVariantMap::const_iterator iter = customValues.begin(); iter != customValues.end(); ++iter) + { + query.addQueryItem(iter.key(), iter.value().toString()); + } +} + +/** + * Sent screen view is called when the user changed the applications view. + * These action of the user should be noticed and reported. Therefore + * a QUrlQuery is build in this method. It holts all the parameter for + * a http POST. The UrlQuery will be stored in a message Queue. + */ +void GAnalytics::sendScreenView(const QString &screenName, const QVariantMap &customValues) +{ + d->logMessage(Info, QString("ScreenView: %1").arg(screenName)); + + QUrlQuery query = d->buildStandardPostQuery("screenview"); + query.addQueryItem("cd", screenName); + query.addQueryItem("an", d->m_appName); + query.addQueryItem("av", d->m_appVersion); + appendCustomValues(query, customValues); + + d->enqueQueryWithCurrentTime(query); +} + +/** + * This method is called whenever a button was pressed in the application. + * A query for a POST message will be created to report this event. The + * created query will be stored in a message queue. + */ +void GAnalytics::sendEvent(const QString &category, const QString &action, const QString &label, const QVariant &value, const QVariantMap &customValues) +{ + QUrlQuery query = d->buildStandardPostQuery("event"); + query.addQueryItem("an", d->m_appName); + query.addQueryItem("av", d->m_appVersion); + query.addQueryItem("ec", category); + query.addQueryItem("ea", action); + if (!label.isEmpty()) + query.addQueryItem("el", label); + if (value.isValid()) + query.addQueryItem("ev", value.toString()); + + appendCustomValues(query, customValues); + + d->enqueQueryWithCurrentTime(query); +} + +/** + * Method is called after an exception was raised. It builds a + * query for a POST message. These query will be stored in a + * message queue. + */ +void GAnalytics::sendException(const QString &exceptionDescription, bool exceptionFatal, const QVariantMap &customValues) +{ + QUrlQuery query = d->buildStandardPostQuery("exception"); + query.addQueryItem("an", d->m_appName); + query.addQueryItem("av", d->m_appVersion); + + query.addQueryItem("exd", exceptionDescription); + + if (exceptionFatal) + { + query.addQueryItem("exf", "1"); + } + else + { + query.addQueryItem("exf", "0"); + } + appendCustomValues(query, customValues); + + d->enqueQueryWithCurrentTime(query); +} + +/** + * Session starts. This event will be sent by a POST message. + * Query is setup in this method and stored in the message + * queue. + */ +void GAnalytics::startSession() +{ + QVariantMap customValues; + customValues.insert("sc", "start"); + sendEvent("Session", "Start", QString(), QVariant(), customValues); +} + +/** + * Session ends. This event will be sent by a POST message. + * Query is setup in this method and stored in the message + * queue. + */ +void GAnalytics::endSession() +{ + QVariantMap customValues; + customValues.insert("sc", "end"); + sendEvent("Session", "End", QString(), QVariant(), customValues); +} + +/** + * Qut stream to persist class GAnalytics. + */ +QDataStream &operator<<(QDataStream &outStream, const GAnalytics &analytics) +{ + outStream << analytics.d->persistMessageQueue(); + + return outStream; +} + +/** + * In stream to read GAnalytics from file. + */ +QDataStream &operator>>(QDataStream &inStream, GAnalytics &analytics) +{ + QList dataList; + inStream >> dataList; + analytics.d->readMessagesFromFile(dataList); + + return inStream; +} diff --git a/libraries/ganalytics/src/ganalytics_worker.cpp b/libraries/ganalytics/src/ganalytics_worker.cpp new file mode 100644 index 00000000..3dbbb50a --- /dev/null +++ b/libraries/ganalytics/src/ganalytics_worker.cpp @@ -0,0 +1,252 @@ +#include "ganalytics.h" +#include "ganalytics_worker.h" +#include "sys.h" + +#include +#include +#include + +#include +#include + +const QLatin1String GAnalyticsWorker::dateTimeFormat("yyyy,MM,dd-hh:mm::ss:zzz"); + +GAnalyticsWorker::GAnalyticsWorker(GAnalytics *parent) + : QObject(parent), q(parent), m_logLevel(GAnalytics::Error), m_isSending(false) +{ + m_appName = QCoreApplication::instance()->applicationName(); + m_appVersion = QCoreApplication::instance()->applicationVersion(); + m_request.setUrl(QUrl("https://www.google-analytics.com/collect")); + m_request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + m_request.setHeader(QNetworkRequest::UserAgentHeader, getUserAgent()); + + m_language = QLocale::system().name().toLower().replace("_", "-"); + m_screenResolution = getScreenResolution(); + + m_timer.start(30000); + connect(&m_timer, &QTimer::timeout, this, &GAnalyticsWorker::postMessage); +} + +void GAnalyticsWorker::logMessage(GAnalytics::LogLevel level, const QString &message) +{ + if (m_logLevel > level) + { + return; + } + + qDebug() << "[Analytics]" << message; +} + +/** + * Build the POST query. Adds all parameter to the query + * which are used in every POST. + * @param type Type of POST message. The event which is to post. + * @return query Most used parameter in a query for a POST. + */ +QUrlQuery GAnalyticsWorker::buildStandardPostQuery(const QString &type) +{ + QUrlQuery query; + query.addQueryItem("v", "1"); + query.addQueryItem("tid", m_trackingID); + query.addQueryItem("cid", m_clientID); + if (!m_userID.isEmpty()) + { + query.addQueryItem("uid", m_userID); + } + query.addQueryItem("t", type); + query.addQueryItem("ul", m_language); + query.addQueryItem("vp", m_viewportSize); + query.addQueryItem("sr", m_screenResolution); + return query; +} + +/** + * Get primary screen resolution. + * @return A QString like "800x600". + */ +QString GAnalyticsWorker::getScreenResolution() +{ + QScreen *screen = QGuiApplication::primaryScreen(); + QSize size = screen->size(); + + return QString("%1x%2").arg(size.width()).arg(size.height()); +} + +/** + * Try to gain information about the system where this application + * is running. It needs to get the name and version of the operating + * system, the language and screen resolution. + * All this information will be send in POST messages. + * @return agent A QString with all the information formatted for a POST message. + */ +QString GAnalyticsWorker::getUserAgent() +{ + QString locale = QLocale::system().name(); + QString system = Sys::getSystemInfo(); + + return QString("%1/%2 (%3; %4) GAnalytics/1.0 (Qt/%5)").arg(m_appName).arg(m_appVersion).arg(system).arg(locale).arg(QT_VERSION_STR); +} + +/** + * The message queue contains a list of QueryBuffer object. + * QueryBuffer holds a QUrlQuery object and a QDateTime object. + * These both object are freed from the buffer object and + * inserted as QString objects in a QList. + * @return dataList The list with concartinated queue data. + */ +QList GAnalyticsWorker::persistMessageQueue() +{ + QList dataList; + foreach (QueryBuffer buffer, m_messageQueue) + { + dataList << buffer.postQuery.toString(); + dataList << buffer.time.toString(dateTimeFormat); + } + return dataList; +} + +/** + * Reads persistent messages from a file. + * Gets all message data as a QList. + * Two lines in the list build a QueryBuffer object. + */ +void GAnalyticsWorker::readMessagesFromFile(const QList &dataList) +{ + QListIterator iter(dataList); + while (iter.hasNext()) + { + QString queryString = iter.next(); + QString dateString = iter.next(); + QUrlQuery query; + query.setQuery(queryString); + QDateTime dateTime = QDateTime::fromString(dateString, dateTimeFormat); + QueryBuffer buffer; + buffer.postQuery = query; + buffer.time = dateTime; + m_messageQueue.enqueue(buffer); + } +} + +/** + * Takes a QUrlQuery object and wrapp it together with + * a QTime object into a QueryBuffer struct. These struct + * will be stored in the message queue. + */ +void GAnalyticsWorker::enqueQueryWithCurrentTime(const QUrlQuery &query) +{ + QueryBuffer buffer; + buffer.postQuery = query; + buffer.time = QDateTime::currentDateTime(); + + m_messageQueue.enqueue(buffer); +} + +/** + * Change status of class. Emit signal that status was changed. + */ +void GAnalyticsWorker::setIsSending(bool doSend) +{ + if (doSend) + { + m_timer.stop(); + } + else + { + m_timer.start(); + } + + bool changed = (m_isSending != doSend); + + m_isSending = doSend; + + if (changed) + { + emit q->isSendingChanged(m_isSending); + } +} + +/** + * This function is called by a timer interval. + * The function tries to send a messages from the queue. + * If message was successfully send then this function + * will be called back to send next message. + * If message queue contains more than one message then + * the connection will kept open. + * The message POST is asyncroniously when the server + * answered a signal will be emitted. + */ +void GAnalyticsWorker::postMessage() +{ + if (m_messageQueue.isEmpty()) + { + setIsSending(false); + return; + } + else + { + setIsSending(true); + } + + QString connection = "close"; + if (m_messageQueue.count() > 1) + { + connection = "keep-alive"; + } + + QueryBuffer buffer = m_messageQueue.head(); + QDateTime sendTime = QDateTime::currentDateTime(); + qint64 timeDiff = buffer.time.msecsTo(sendTime); + + if (timeDiff > fourHours) + { + // too old. + m_messageQueue.dequeue(); + emit postMessage(); + return; + } + + buffer.postQuery.addQueryItem("qt", QString::number(timeDiff)); + m_request.setRawHeader("Connection", connection.toUtf8()); + m_request.setHeader(QNetworkRequest::ContentLengthHeader, buffer.postQuery.toString().length()); + + // Create a new network access manager if we don't have one yet + if (networkManager == NULL) + { + networkManager = new QNetworkAccessManager(this); + } + + QNetworkReply *reply = networkManager->post(m_request, buffer.postQuery.query(QUrl::EncodeUnicode).toUtf8()); + connect(reply, SIGNAL(finished()), this, SLOT(postMessageFinished())); +} + +/** + * NetworkAccsessManager has finished to POST a message. + * If POST message was successfully send then the message + * query should be removed from queue. + * SIGNAL "postMessage" will be emitted to send next message + * if there is any. + * If message couldn't be send then next try is when the + * timer emits its signal. + */ +void GAnalyticsWorker::postMessageFinished() +{ + QNetworkReply *reply = qobject_cast(sender()); + + int httpStausCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (httpStausCode < 200 || httpStausCode > 299) + { + logMessage(GAnalytics::Error, QString("Error posting message: %s").arg(reply->errorString())); + + // An error ocurred. + setIsSending(false); + return; + } + else + { + logMessage(GAnalytics::Debug, "Message sent"); + } + + m_messageQueue.dequeue(); + postMessage(); + reply->deleteLater(); +} diff --git a/libraries/ganalytics/src/ganalytics_worker.h b/libraries/ganalytics/src/ganalytics_worker.h new file mode 100644 index 00000000..280f9ab2 --- /dev/null +++ b/libraries/ganalytics/src/ganalytics_worker.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include +#include +#include + +struct QueryBuffer +{ + QUrlQuery postQuery; + QDateTime time; +}; + +class GAnalyticsWorker : public QObject +{ + Q_OBJECT + +public: + explicit GAnalyticsWorker(GAnalytics *parent = 0); + + GAnalytics *q; + + QNetworkAccessManager *networkManager = nullptr; + + QQueue m_messageQueue; + QTimer m_timer; + QNetworkRequest m_request; + GAnalytics::LogLevel m_logLevel; + + QString m_trackingID; + QString m_clientID; + QString m_userID; + QString m_appName; + QString m_appVersion; + QString m_language; + QString m_screenResolution; + QString m_viewportSize; + + bool m_isSending; + + const static int fourHours = 4 * 60 * 60 * 1000; + const static QLatin1String dateTimeFormat; + +public: + void logMessage(GAnalytics::LogLevel level, const QString &message); + + QUrlQuery buildStandardPostQuery(const QString &type); + QString getScreenResolution(); + QString getUserAgent(); + QList persistMessageQueue(); + void readMessagesFromFile(const QList &dataList); + + void enqueQueryWithCurrentTime(const QUrlQuery &query); + void setIsSending(bool doSend); + +public slots: + void postMessage(); + void postMessageFinished(); +}; + diff --git a/libraries/ganalytics/src/sys.h b/libraries/ganalytics/src/sys.h new file mode 100644 index 00000000..ef37cbde --- /dev/null +++ b/libraries/ganalytics/src/sys.h @@ -0,0 +1,11 @@ +#pragma once +#include + +namespace Sys +{ +/** + * Get operation system name and version. + * @return os A QString with the name and version of the operating system. + */ +QString getSystemInfo(); +} diff --git a/libraries/ganalytics/src/sys_apple.cpp b/libraries/ganalytics/src/sys_apple.cpp new file mode 100644 index 00000000..cfaea839 --- /dev/null +++ b/libraries/ganalytics/src/sys_apple.cpp @@ -0,0 +1,114 @@ +#include "sys.h" + +QString Sys::getSystemInfo() +{ + QSysInfo::MacVersion version = QSysInfo::macVersion(); + QString os; + switch (version) + { + case QSysInfo::MV_9: + os = "Macintosh; Mac OS 9"; + break; + case QSysInfo::MV_10_0: + os = "Macintosh; Mac OS 10.0"; + break; + case QSysInfo::MV_10_1: + os = "Macintosh; Mac OS 10.1"; + break; + case QSysInfo::MV_10_2: + os = "Macintosh; Mac OS 10.2"; + break; + case QSysInfo::MV_10_3: + os = "Macintosh; Mac OS 10.3"; + break; + case QSysInfo::MV_10_4: + os = "Macintosh; Mac OS 10.4"; + break; + case QSysInfo::MV_10_5: + os = "Macintosh; Mac OS 10.5"; + break; + case QSysInfo::MV_10_6: + os = "Macintosh; Mac OS 10.6"; + break; + case QSysInfo::MV_10_7: + os = "Macintosh; Mac OS 10.7"; + break; + case QSysInfo::MV_10_8: + os = "Macintosh; Mac OS 10.8"; + break; + case QSysInfo::MV_10_9: + os = "Macintosh; Mac OS 10.9"; + break; + case QSysInfo::MV_10_10: + os = "Macintosh; Mac OS 10.10"; + break; + case QSysInfo::MV_10_11: + os = "Macintosh; Mac OS 10.11"; + break; +#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) + case QSysInfo::MV_10_12: + os = "Macintosh; Mac OS 10.12"; + break; +#endif + case QSysInfo::MV_Unknown: + os = "Macintosh; Mac OS unknown"; + break; + case QSysInfo::MV_IOS_5_0: + os = "iPhone; iOS 5.0"; + break; + case QSysInfo::MV_IOS_5_1: + os = "iPhone; iOS 5.1"; + break; + case QSysInfo::MV_IOS_6_0: + os = "iPhone; iOS 6.0"; + break; + case QSysInfo::MV_IOS_6_1: + os = "iPhone; iOS 6.1"; + break; + case QSysInfo::MV_IOS_7_0: + os = "iPhone; iOS 7.0"; + break; + case QSysInfo::MV_IOS_7_1: + os = "iPhone; iOS 7.1"; + break; + case QSysInfo::MV_IOS_8_0: + os = "iPhone; iOS 8.0"; + break; + case QSysInfo::MV_IOS_8_1: + os = "iPhone; iOS 8.1"; + break; + case QSysInfo::MV_IOS_8_2: + os = "iPhone; iOS 8.2"; + break; + case QSysInfo::MV_IOS_8_3: + os = "iPhone; iOS 8.3"; + break; + case QSysInfo::MV_IOS_8_4: + os = "iPhone; iOS 8.4"; + break; + case QSysInfo::MV_IOS_9_0: + os = "iPhone; iOS 9.0"; + break; +#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) + case QSysInfo::MV_IOS_9_1: + os = "iPhone; iOS 9.1"; + break; + case QSysInfo::MV_IOS_9_2: + os = "iPhone; iOS 9.2"; + break; + case QSysInfo::MV_IOS_9_3: + os = "iPhone; iOS 9.3"; + break; + case QSysInfo::MV_IOS_10_0: + os = "iPhone; iOS 10.0"; + break; +#endif + case QSysInfo::MV_IOS: + os = "iPhone; iOS unknown"; + break; + default: + os = "Macintosh"; + break; + } + return os; +} diff --git a/libraries/ganalytics/src/sys_unix.cpp b/libraries/ganalytics/src/sys_unix.cpp new file mode 100644 index 00000000..b4dd9b2b --- /dev/null +++ b/libraries/ganalytics/src/sys_unix.cpp @@ -0,0 +1,13 @@ +#include "sys.h" + +#include + +QString Sys::getSystemInfo() +{ + struct utsname buf; + uname(&buf); + QString system(buf.sysname); + QString release(buf.release); + + return system + "; " + release; +} diff --git a/libraries/ganalytics/src/sys_win32.cpp b/libraries/ganalytics/src/sys_win32.cpp new file mode 100644 index 00000000..42c0f280 --- /dev/null +++ b/libraries/ganalytics/src/sys_win32.cpp @@ -0,0 +1,50 @@ +#include "sys.h" + +QString Sys::getSystemInfo() +{ + QSysInfo::WinVersion version = QSysInfo::windowsVersion(); + QString os("Windows; "); + switch (version) + { + case QSysInfo::WV_95: + os += "Win 95"; + break; + case QSysInfo::WV_98: + os += "Win 98"; + break; + case QSysInfo::WV_Me: + os += "Win ME"; + break; + case QSysInfo::WV_NT: + os += "Win NT"; + break; + case QSysInfo::WV_2000: + os += "Win 2000"; + break; + case QSysInfo::WV_2003: + os += "Win Server 2003"; + break; + case QSysInfo::WV_VISTA: + os += "Win Vista"; + break; + case QSysInfo::WV_WINDOWS7: + os += "Win 7"; + break; + case QSysInfo::WV_WINDOWS8: + os += "Win 8"; + break; + case QSysInfo::WV_WINDOWS8_1: + os += "Win 8.1"; + break; +#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) + case QSysInfo::WV_WINDOWS10: + os += "Win 10"; + break; +#endif + default: + os = "Windows; unknown"; + break; + } + return os; +} + -- cgit v1.2.3