From eaf9d6533901481fad6d88be5a6c78df2b10fe12 Mon Sep 17 00:00:00 2001 From: Roman Telezhynskyi Date: Tue, 27 Jun 2023 14:15:21 +0300 Subject: [PATCH] System to collect usage statistic. --- share/translations/translations.pro | 4 +- .../puzzlepreferencesconfigurationpage.cpp | 9 + .../puzzlepreferencesconfigurationpage.ui | 45 +- src/app/puzzle/puzzle.pro | 9 + src/app/puzzle/puzzle.qbs | 1 + src/app/puzzle/vpapplication.cpp | 26 ++ src/app/puzzle/vpmainwindow.cpp | 38 +- src/app/puzzle/vpmainwindow.h | 2 +- .../tapepreferencesconfigurationpage.cpp | 21 +- .../tapepreferencesconfigurationpage.ui | 419 ++++++++++-------- src/app/tape/mapplication.cpp | 27 ++ src/app/tape/tape.pro | 9 + src/app/tape/tape.qbs | 1 + src/app/tape/tmainwindow.cpp | 38 +- src/app/tape/tmainwindow.h | 2 +- src/app/valentina/core/vapplication.cpp | 28 +- .../preferencesconfigurationpage.cpp | 9 +- .../preferencesconfigurationpage.ui | 37 ++ src/app/valentina/mainwindow.cpp | 38 +- src/app/valentina/mainwindow.h | 2 +- src/app/valentina/valentina.pro | 9 + src/app/valentina/valentina.qbs | 1 + src/libs/libs.pri | 3 + src/libs/libs.pro | 3 +- src/libs/libs.qbs | 1 + src/libs/vganalytics/def.h | 36 ++ src/libs/vganalytics/vganalytics.cpp | 306 +++++++++++++ src/libs/vganalytics/vganalytics.h | 99 +++++ src/libs/vganalytics/vganalytics.pri | 8 + src/libs/vganalytics/vganalytics.pro | 80 ++++ src/libs/vganalytics/vganalytics.qbs | 16 + src/libs/vganalytics/vganalyticsworker.cpp | 281 ++++++++++++ src/libs/vganalytics/vganalyticsworker.h | 97 ++++ src/libs/vganalytics/warnings.pri | 82 ++++ .../dialogs/dialogaskcollectstatistic.cpp | 49 ++ .../vmisc/dialogs/dialogaskcollectstatistic.h | 57 +++ .../dialogs/dialogaskcollectstatistic.ui | 103 +++++ src/libs/vmisc/vabstractapplication.cpp | 8 + src/libs/vmisc/vabstractapplication.h | 5 + src/libs/vmisc/vcommonsettings.cpp | 59 ++- src/libs/vmisc/vcommonsettings.h | 9 + src/libs/vmisc/vmisc.pri | 3 + src/libs/vmisc/vmisc.qbs | 5 +- 43 files changed, 1871 insertions(+), 214 deletions(-) create mode 100644 src/libs/vganalytics/def.h create mode 100644 src/libs/vganalytics/vganalytics.cpp create mode 100644 src/libs/vganalytics/vganalytics.h create mode 100644 src/libs/vganalytics/vganalytics.pri create mode 100644 src/libs/vganalytics/vganalytics.pro create mode 100644 src/libs/vganalytics/vganalytics.qbs create mode 100644 src/libs/vganalytics/vganalyticsworker.cpp create mode 100644 src/libs/vganalytics/vganalyticsworker.h create mode 100644 src/libs/vganalytics/warnings.pri create mode 100644 src/libs/vmisc/dialogs/dialogaskcollectstatistic.cpp create mode 100644 src/libs/vmisc/dialogs/dialogaskcollectstatistic.h create mode 100644 src/libs/vmisc/dialogs/dialogaskcollectstatistic.ui diff --git a/share/translations/translations.pro b/share/translations/translations.pro index 0109a1d4e..6a3894b94 100644 --- a/share/translations/translations.pro +++ b/share/translations/translations.pro @@ -22,7 +22,8 @@ DEPENDPATH += \ ../../src/libs/vtools \ ../../src/libs/vformat \ ../../src/libs/fervor \ - ../../src/libs/vwidgets + ../../src/libs/vwidgets \ + ../../src/libs/vganalytics include(../../src/app/valentina/valentina.pri) include(../../src/app/tape/tape.pri) @@ -39,6 +40,7 @@ include(../../src/libs/vtools/vtools.pri) include(../../src/libs/vformat/vformat.pri) include(../../src/libs/fervor/fervor.pri) include(../../src/libs/vwidgets/vwidgets.pri) +include(../../src/libs/vganalytics/vganalytics.pri) # Add here path to new translation file with name "valentina_*_*.ts" if you want to add new language. # Same paths in variable INSTALL_TRANSLATIONS (translations.pri). diff --git a/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.cpp b/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.cpp index 526cf228e..1b49dd157 100644 --- a/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.cpp +++ b/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.cpp @@ -33,6 +33,8 @@ #include "../vmisc/backport/qoverload.h" #endif // QT_VERSION < QT_VERSION_CHECK(5, 7, 0) +#include "../vganalytics/vganalytics.h" + //--------------------------------------------------------------------------------------------------------------------- PuzzlePreferencesConfigurationPage::PuzzlePreferencesConfigurationPage(QWidget *parent) : QWidget(parent), @@ -89,6 +91,9 @@ PuzzlePreferencesConfigurationPage::PuzzlePreferencesConfigurationPage(QWidget * ui->doubleSpinBoxAcceleration->setMinimum(VCommonSettings::scrollingAccelerationMin); ui->doubleSpinBoxAcceleration->setMaximum(VCommonSettings::scrollingAccelerationMax); ui->doubleSpinBoxAcceleration->setValue(settings->GetScrollingAcceleration()); + + // Tab Privacy + ui->checkBoxSendUsageStatistics->setChecked(settings->IsCollectStatistic()); } //--------------------------------------------------------------------------------------------------------------------- @@ -166,6 +171,10 @@ auto PuzzlePreferencesConfigurationPage::Apply() -> QStringList settings->SetWheelMouseScale(ui->doubleSpinBoxWheel->value()); settings->SetScrollingAcceleration(ui->doubleSpinBoxAcceleration->value()); + // Tab Privacy + settings->SetCollectStatistic(ui->checkBoxSendUsageStatistics->isChecked()); + VGAnalytics::Instance()->Enable(ui->checkBoxSendUsageStatistics->isChecked()); + return preferences; } diff --git a/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.ui b/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.ui index 1aced40f9..f90d997a1 100644 --- a/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.ui +++ b/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.ui @@ -6,8 +6,8 @@ 0 0 - 545 - 696 + 539 + 702 @@ -34,8 +34,8 @@ 0 0 - 503 - 627 + 497 + 633 @@ -424,6 +424,43 @@ This option will take an affect after restart. + + + Privacy + + + + + + Send usage statistics + + + + + + + Please help to improve Valentina's quality by automatically sending usage statistics. Sent data contains <span style=" font-weight:700;">no potentially sensitive information</span> like user names, email addresses, file contents or file paths. + + + true + + + + + + + Qt::Vertical + + + + 20 + 540 + + + + + + diff --git a/src/app/puzzle/puzzle.pro b/src/app/puzzle/puzzle.pro index ef824ee9c..653876c8e 100644 --- a/src/app/puzzle/puzzle.pro +++ b/src/app/puzzle/puzzle.pro @@ -282,6 +282,15 @@ DEPENDPATH += $$PWD/../../libs/vdxf win32:!win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vdxf/$${DESTDIR}/vdxf.lib else:unix|win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vdxf/$${DESTDIR}/libvdxf.a +# VGAnalytics static library +unix|win32: LIBS += -L$$OUT_PWD/../../libs/vganalytics/$${DESTDIR}/ -lvganalytics + +INCLUDEPATH += $$PWD/../../libs/vganalytics +DEPENDPATH += $$PWD/../../libs/vganalytics + +win32:!win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vganalytics/$${DESTDIR}/vganalytics.lib +else:unix|win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vganalytics/$${DESTDIR}/libvganalytics.a + # QMuParser library win32:CONFIG(release, debug|release): LIBS += -L$${OUT_PWD}/../../libs/qmuparser/$${DESTDIR} -lqmuparser2 else:win32:CONFIG(debug, debug|release): LIBS += -L$${OUT_PWD}/../../libs/qmuparser/$${DESTDIR} -lqmuparser2 diff --git a/src/app/puzzle/puzzle.qbs b/src/app/puzzle/puzzle.qbs index ece097a72..b07a5c92e 100644 --- a/src/app/puzzle/puzzle.qbs +++ b/src/app/puzzle/puzzle.qbs @@ -12,6 +12,7 @@ VToolApp { Depends { name: "VWidgetsLib" } Depends { name: "FervorLib" } Depends { name: "multibundle"; } + Depends { name: "VGAnalyticsLib" } name: "Puzzle" buildconfig.appTarget: qbs.targetOS.contains("macos") ? "Puzzle" : "puzzle" diff --git a/src/app/puzzle/vpapplication.cpp b/src/app/puzzle/vpapplication.cpp index d55b94c85..81d40cbb7 100644 --- a/src/app/puzzle/vpapplication.cpp +++ b/src/app/puzzle/vpapplication.cpp @@ -32,6 +32,8 @@ #include "../ifc/exception/vexceptionemptyparameter.h" #include "../ifc/exception/vexceptionobjecterror.h" #include "../ifc/exception/vexceptionwrongid.h" +#include "../vganalytics/def.h" +#include "../vganalytics/vganalytics.h" #include "../vmisc/vsysexits.h" #include "version.h" #include "vpmainwindow.h" @@ -277,6 +279,17 @@ VPApplication::VPApplication(int &argc, char **argv) //--------------------------------------------------------------------------------------------------------------------- VPApplication::~VPApplication() { + if (settings->IsCollectStatistic()) + { + auto *statistic = VGAnalytics::Instance(); + + QString clientID = settings->GetClientID(); + if (!clientID.isEmpty()) + { + statistic->SendAppCloseEvent(m_uptimeTimer.elapsed()); + } + } + qDeleteAll(m_mainWindows); } @@ -430,6 +443,19 @@ void VPApplication::InitOptions() QIcon::setThemeName(QStringLiteral("win.icon.theme")); } ActivateDarkMode(); + + auto *statistic = VGAnalytics::Instance(); + QString clientID = settings->GetClientID(); + if (clientID.isEmpty()) + { + clientID = QUuid::createUuid().toString(); + settings->SetClientID(clientID); + } + statistic->SetClientID(clientID); + statistic->SetGUILanguage(settings->GetLocale()); + statistic->SetMeasurementId(GA_MEASUREMENT_ID); + statistic->SetApiSecret(GA_API_SECRET); + statistic->Enable(settings->IsCollectStatistic()); } //--------------------------------------------------------------------------------------------------------------------- diff --git a/src/app/puzzle/vpmainwindow.cpp b/src/app/puzzle/vpmainwindow.cpp index acf099064..c65f7297c 100644 --- a/src/app/puzzle/vpmainwindow.cpp +++ b/src/app/puzzle/vpmainwindow.cpp @@ -47,6 +47,7 @@ #include "../vlayout/vlayoutexporter.h" #include "../vlayout/vprintlayout.h" #include "../vlayout/vrawlayout.h" +#include "../vmisc/dialogs/dialogaskcollectstatistic.h" #include "../vmisc/dialogs/dialogselectlanguage.h" #include "../vmisc/lambdaconstants.h" #include "../vmisc/projectversion.h" @@ -67,6 +68,7 @@ #if QT_VERSION < QT_VERSION_CHECK(5, 7, 0) #include "../vmisc/backport/qoverload.h" #endif // QT_VERSION < QT_VERSION_CHECK(5, 7, 0) +#include "../vganalytics/vganalytics.h" #include "layout/vppiece.h" #include "vptilefactory.h" @@ -389,7 +391,7 @@ VPMainWindow::VPMainWindow(const VPCommandLinePtr &cmd, QWidget *parent) if (m_cmd->IsGuiEnabled()) { - QTimer::singleShot(V_SECONDS(1), this, &VPMainWindow::SetDefaultGUILanguage); + QTimer::singleShot(V_SECONDS(1), this, &VPMainWindow::AskDefaultSettings); } } @@ -4536,7 +4538,7 @@ void VPMainWindow::RemoveWatermark() } //--------------------------------------------------------------------------------------------------------------------- -void VPMainWindow::SetDefaultGUILanguage() +void VPMainWindow::AskDefaultSettings() { if (m_cmd->IsGuiEnabled()) { @@ -4554,6 +4556,38 @@ void VPMainWindow::SetDefaultGUILanguage() VAbstractApplication::VApp()->LoadTranslation(locale); } } + + if (settings->IsAskCollectStatistic()) + { + DialogAskCollectStatistic dialog(this); + if (dialog.exec() == QDialog::Accepted) + { + settings->SetCollectStatistic(dialog.CollectStatistic()); + } + + settings->SetAskCollectStatistic(false); + } + + if (settings->IsCollectStatistic()) + { + auto *statistic = VGAnalytics::Instance(); + statistic->SetGUILanguage(settings->GetLocale()); + + QString clientID = settings->GetClientID(); + bool freshID = false; + if (clientID.isEmpty()) + { + clientID = QUuid::createUuid().toString(); + settings->SetClientID(clientID); + statistic->SetClientID(clientID); + freshID = true; + } + + statistic->Enable(true); + + const qint64 uptime = VPApplication::VApp()->AppUptime(); + freshID ? statistic->SendAppFreshInstallEvent(uptime) : statistic->SendAppStartEvent(uptime); + } } } diff --git a/src/app/puzzle/vpmainwindow.h b/src/app/puzzle/vpmainwindow.h index e00c71d57..87dcde859 100644 --- a/src/app/puzzle/vpmainwindow.h +++ b/src/app/puzzle/vpmainwindow.h @@ -286,7 +286,7 @@ private slots: void AboutToShowDockMenu(); #endif //defined(Q_OS_MAC) - void SetDefaultGUILanguage(); + void AskDefaultSettings(); void HorizontalScaleChanged(double value); void VerticalScaleChanged(double value); diff --git a/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.cpp b/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.cpp index a4140594d..428470ade 100644 --- a/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.cpp +++ b/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.cpp @@ -35,6 +35,7 @@ #include "../vmisc/backport/qoverload.h" #endif // QT_VERSION < QT_VERSION_CHECK(5, 7, 0) #include "../qmuparser/qmudef.h" +#include "../vganalytics/vganalytics.h" //--------------------------------------------------------------------------------------------------------------------- TapePreferencesConfigurationPage::TapePreferencesConfigurationPage(QWidget *parent) @@ -53,13 +54,13 @@ TapePreferencesConfigurationPage::TapePreferencesConfigurationPage(QWidget *pare VTapeSettings *settings = MApplication::VApp()->TapeSettings(); //-------------------- Decimal separator setup - ui->osOptionCheck->setChecked(MApplication::VApp()->TapeSettings()->GetOsSeparator()); + ui->osOptionCheck->setChecked(settings->GetOsSeparator()); // Theme - ui->darkModeCheck->setChecked(MApplication::VApp()->TapeSettings()->GetDarkMode()); + ui->darkModeCheck->setChecked(settings->GetDarkMode()); // Native dialogs - ui->checkBoxDontUseNativeDialog->setChecked(MApplication::VApp()->TapeSettings()->IsDontUseNativeDialog()); + ui->checkBoxDontUseNativeDialog->setChecked(settings->IsDontUseNativeDialog()); //---------------------- Pattern making system ui->systemBookValueLabel->setFixedHeight(4 * QFontMetrics(ui->systemBookValueLabel->font()).lineSpacing()); @@ -77,7 +78,7 @@ TapePreferencesConfigurationPage::TapePreferencesConfigurationPage(QWidget *pare }); // set default pattern making system - int index = ui->systemCombo->findData(MApplication::VApp()->TapeSettings()->GetPMSystemCode()); + int index = ui->systemCombo->findData(settings->GetPMSystemCode()); if (index != -1) { ui->systemCombo->setCurrentIndex(index); @@ -88,11 +89,13 @@ TapePreferencesConfigurationPage::TapePreferencesConfigurationPage(QWidget *pare []() { MApplication::VApp()->TapeSettings()->SetConfirmFormatRewriting(true); }); //----------------------- Toolbar - ui->toolBarStyleCheck->setChecked(MApplication::VApp()->TapeSettings()->GetToolBarStyle()); + ui->toolBarStyleCheck->setChecked(settings->GetToolBarStyle()); //----------------------- Update - ui->checkBoxAutomaticallyCheckUpdates->setChecked( - MApplication::VApp()->TapeSettings()->IsAutomaticallyCheckUpdates()); + ui->checkBoxAutomaticallyCheckUpdates->setChecked(settings->IsAutomaticallyCheckUpdates()); + + // Tab Privacy + ui->checkBoxSendUsageStatistics->setChecked(settings->IsCollectStatistic()); } //--------------------------------------------------------------------------------------------------------------------- @@ -144,6 +147,10 @@ auto TapePreferencesConfigurationPage::Apply() -> QStringList settings->SetAutomaticallyCheckUpdates(ui->checkBoxAutomaticallyCheckUpdates->isChecked()); } + // Tab Privacy + settings->SetCollectStatistic(ui->checkBoxSendUsageStatistics->isChecked()); + VGAnalytics::Instance()->Enable(ui->checkBoxSendUsageStatistics->isChecked()); + return preferences; } diff --git a/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.ui b/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.ui index 8747455c0..272d598d6 100644 --- a/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.ui +++ b/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.ui @@ -6,209 +6,260 @@ 0 0 - 544 - 636 + 564 + 686 Configuration - + - - - true + + + 0 - - - - 0 - 0 - 524 - 616 - - - + + + General + + - - - Language + + + true - - - QFormLayout::ExpandingFieldsGrow + + + + 0 + 0 + 522 + 617 + - - - - GUI language: - - - - - - - - - - Decimal separator parts: - - - - - - - < With OS options > - - - true - - - - + + + + + Language + + + + QFormLayout::ExpandingFieldsGrow + + + + + GUI language: + + + + + + + + + + Decimal separator parts: + + + + + + + < With OS options > + + + true + + + + + + + + + + Pattern making system + + + + QFormLayout::ExpandingFieldsGrow + + + + + Pattern making system: + + + + + + + + + + Author: + + + + + + + author + + + + + + + Book: + + + + + + + + 0 + 0 + + + + true + + + + + + + + + + Measurements editing + + + + + + + 0 + 0 + + + + Reset warnings + + + + + + + + + + Toolbar + + + + + + The text appears under the icon (recommended for beginners). + + + true + + + + + + + + + + User Interface + + + + + + Activate dark mode + + + + + + + Don't use the native file dialog + + + + + + + + + + Update + + + + + + Automatically check for updates each time the application starts + + + Automatically check for updates + + + true + + + + + + + + + + + + + + + Privacy + + + + + + Send usage statistics + - - - Pattern making system + + + Please help to improve Valentina's quality by automatically sending usage statistics. Sent data contains <span style=" font-weight:700;">no potentially sensitive information</span> like user names, email addresses, file contents or file paths. + + + true - - - QFormLayout::ExpandingFieldsGrow - - - - - Pattern making system: - - - - - - - - - - Author: - - - - - - - author - - - - - - - Book: - - - - - - - - 0 - 0 - - - - true - - - - - - - Measurements editing + + + Qt::Vertical - - - - - - 0 - 0 - - - - Reset warnings - - - - - - - - - - Toolbar + + + 20 + 530 + - - - - - The text appears under the icon (recommended for beginners). - - - true - - - - - - - - - - User Interface - - - - - - Activate dark mode - - - - - - - Don't use the native file dialog - - - - - - - - - - Update - - - - - - Automatically check for updates each time the application starts - - - Automatically check for updates - - - true - - - - - + diff --git a/src/app/tape/mapplication.cpp b/src/app/tape/mapplication.cpp index 2d879f391..8d0e1f0c5 100644 --- a/src/app/tape/mapplication.cpp +++ b/src/app/tape/mapplication.cpp @@ -32,6 +32,8 @@ #include "../ifc/exception/vexceptionemptyparameter.h" #include "../ifc/exception/vexceptionobjecterror.h" #include "../ifc/exception/vexceptionwrongid.h" +#include "../vganalytics/def.h" +#include "../vganalytics/vganalytics.h" #include "../vmisc/projectversion.h" #include "../vmisc/vsysexits.h" #include "tmainwindow.h" @@ -77,6 +79,7 @@ Q_LOGGING_CATEGORY(mApp, "m.application") // NOLINT QT_WARNING_POP #include +#include namespace { @@ -318,6 +321,17 @@ MApplication::MApplication(int &argc, char **argv) //--------------------------------------------------------------------------------------------------------------------- MApplication::~MApplication() { + if (settings->IsCollectStatistic()) + { + auto *statistic = VGAnalytics::Instance(); + + QString clientID = settings->GetClientID(); + if (!clientID.isEmpty()) + { + statistic->SendAppCloseEvent(m_uptimeTimer.elapsed()); + } + } + qDeleteAll(m_mainWindows); delete m_trVars; @@ -473,6 +487,19 @@ void MApplication::InitOptions() } ActivateDarkMode(); QResource::registerResource(diagramsPath()); + + auto *statistic = VGAnalytics::Instance(); + QString clientID = settings->GetClientID(); + if (clientID.isEmpty()) + { + clientID = QUuid::createUuid().toString(); + settings->SetClientID(clientID); + } + statistic->SetClientID(clientID); + statistic->SetGUILanguage(settings->GetLocale()); + statistic->SetMeasurementId(GA_MEASUREMENT_ID); + statistic->SetApiSecret(GA_API_SECRET); + statistic->Enable(settings->IsCollectStatistic()); } //--------------------------------------------------------------------------------------------------------------------- diff --git a/src/app/tape/tape.pro b/src/app/tape/tape.pro index 0bc474357..5670b90fc 100644 --- a/src/app/tape/tape.pro +++ b/src/app/tape/tape.pro @@ -408,6 +408,15 @@ DEPENDPATH += $$PWD/../../libs/vmisc win32:!win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vmisc/$${DESTDIR}/vmisc.lib else:unix|win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vmisc/$${DESTDIR}/libvmisc.a +# VGAnalytics static library +unix|win32: LIBS += -L$$OUT_PWD/../../libs/vganalytics/$${DESTDIR}/ -lvganalytics + +INCLUDEPATH += $$PWD/../../libs/vganalytics +DEPENDPATH += $$PWD/../../libs/vganalytics + +win32:!win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vganalytics/$${DESTDIR}/vganalytics.lib +else:unix|win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vganalytics/$${DESTDIR}/libvganalytics.a + # QMuParser library win32:CONFIG(release, debug|release): LIBS += -L$${OUT_PWD}/../../libs/qmuparser/$${DESTDIR} -lqmuparser2 else:win32:CONFIG(debug, debug|release): LIBS += -L$${OUT_PWD}/../../libs/qmuparser/$${DESTDIR} -lqmuparser2 diff --git a/src/app/tape/tape.qbs b/src/app/tape/tape.qbs index b57540157..eb5403b01 100644 --- a/src/app/tape/tape.qbs +++ b/src/app/tape/tape.qbs @@ -14,6 +14,7 @@ VToolApp { Depends { name: "VToolsLib"; } Depends { name: "ebr" } Depends { name: "multibundle"; } + Depends { name: "VGAnalyticsLib" } Depends { name: "conan.XercesC"; condition: buildconfig.useConanPackages } name: "Tape" diff --git a/src/app/tape/tmainwindow.cpp b/src/app/tape/tmainwindow.cpp index 3b8cb0c51..82fa749ac 100644 --- a/src/app/tape/tmainwindow.cpp +++ b/src/app/tape/tmainwindow.cpp @@ -32,6 +32,7 @@ #include "../ifc/xml/vvitconverter.h" #include "../ifc/xml/vvstconverter.h" #include "../vmisc/compatibility.h" +#include "../vmisc/dialogs/dialogaskcollectstatistic.h" #include "../vmisc/dialogs/dialogexporttocsv.h" #include "../vmisc/qxtcsvmodel.h" #include "../vmisc/vsysexits.h" @@ -56,6 +57,7 @@ #include "../vmisc/backport/qoverload.h" #endif // QT_VERSION < QT_VERSION_CHECK(5, 7, 0) #include "../qmuparser/qmudef.h" +#include "../vganalytics/vganalytics.h" #include "../vmisc/dialogs/dialogselectlanguage.h" #include "../vtools/dialogs/support/dialogeditwrongformula.h" #include "mapplication.h" // Should be last because of definning qApp @@ -304,7 +306,7 @@ TMainWindow::TMainWindow(QWidget *parent) if (MApplication::VApp()->IsAppInGUIMode()) { - QTimer::singleShot(V_SECONDS(1), this, &TMainWindow::SetDefaultGUILanguage); + QTimer::singleShot(V_SECONDS(1), this, &TMainWindow::AskDefaultSettings); } } @@ -2534,7 +2536,7 @@ void TMainWindow::DimensionCustomNames() } //--------------------------------------------------------------------------------------------------------------------- -void TMainWindow::SetDefaultGUILanguage() +void TMainWindow::AskDefaultSettings() { if (MApplication::VApp()->IsAppInGUIMode()) { @@ -2552,6 +2554,38 @@ void TMainWindow::SetDefaultGUILanguage() VAbstractApplication::VApp()->LoadTranslation(locale); } } + + if (settings->IsAskCollectStatistic()) + { + DialogAskCollectStatistic dialog(this); + if (dialog.exec() == QDialog::Accepted) + { + settings->SetCollectStatistic(dialog.CollectStatistic()); + } + + settings->SetAskCollectStatistic(false); + } + + if (settings->IsCollectStatistic()) + { + auto *statistic = VGAnalytics::Instance(); + statistic->SetGUILanguage(settings->GetLocale()); + + QString clientID = settings->GetClientID(); + bool freshID = false; + if (clientID.isEmpty()) + { + clientID = QUuid::createUuid().toString(); + settings->SetClientID(clientID); + statistic->SetClientID(clientID); + freshID = true; + } + + statistic->Enable(true); + + const qint64 uptime = MApplication::VApp()->AppUptime(); + freshID ? statistic->SendAppFreshInstallEvent(uptime) : statistic->SendAppStartEvent(uptime); + } } } diff --git a/src/app/tape/tmainwindow.h b/src/app/tape/tmainwindow.h index b14b6e952..d0b930362 100644 --- a/src/app/tape/tmainwindow.h +++ b/src/app/tape/tmainwindow.h @@ -146,7 +146,7 @@ private slots: void EditDimensionLabels(); void DimensionCustomNames(); - void SetDefaultGUILanguage(); + void AskDefaultSettings(); private: // cppcheck-suppress unknownMacro diff --git a/src/app/valentina/core/vapplication.cpp b/src/app/valentina/core/vapplication.cpp index 6e07238cb..0d47d0574 100644 --- a/src/app/valentina/core/vapplication.cpp +++ b/src/app/valentina/core/vapplication.cpp @@ -36,10 +36,11 @@ #include "../mainwindow.h" #include "../qmuparser/qmuparsererror.h" #include "../version.h" +#include "../vganalytics/def.h" +#include "../vganalytics/vganalytics.h" #include "../vmisc/qt_dispatch/qt_dispatch.h" #include "../vmisc/vsysexits.h" #include "../vmisc/vvalentinasettings.h" -#include #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) #include "../vmisc/backport/text.h" @@ -383,6 +384,18 @@ VApplication::VApplication(int &argc, char **argv) VApplication::~VApplication() { qCDebug(vApp, "Application closing."); + + if (settings->IsCollectStatistic()) + { + auto *statistic = VGAnalytics::Instance(); + + QString clientID = settings->GetClientID(); + if (!clientID.isEmpty()) + { + statistic->SendAppCloseEvent(m_uptimeTimer.elapsed()); + } + } + qInstallMessageHandler(nullptr); // Resore the message handler delete m_trVars; VCommandLine::Reset(); @@ -695,6 +708,19 @@ void VApplication::InitOptions() QIcon::setThemeName(QStringLiteral("win.icon.theme")); } ActivateDarkMode(); + + auto *statistic = VGAnalytics::Instance(); + QString clientID = settings->GetClientID(); + if (clientID.isEmpty()) + { + clientID = QUuid::createUuid().toString(); + settings->SetClientID(clientID); + } + statistic->SetClientID(clientID); + statistic->SetGUILanguage(settings->GetLocale()); + statistic->SetMeasurementId(GA_MEASUREMENT_ID); + statistic->SetApiSecret(GA_API_SECRET); + statistic->Enable(settings->IsCollectStatistic()); } //--------------------------------------------------------------------------------------------------------------------- diff --git a/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.cpp b/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.cpp index 38940cf09..2114efd39 100644 --- a/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.cpp +++ b/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.cpp @@ -31,12 +31,12 @@ #include "../vmisc/literals.h" #include "../vmisc/vvalentinasettings.h" #include "../vpatterndb/pmsystems.h" -#include "def.h" #include "ui_preferencesconfigurationpage.h" #if QT_VERSION < QT_VERSION_CHECK(5, 7, 0) #include "../vmisc/backport/qoverload.h" #endif // QT_VERSION < QT_VERSION_CHECK(5, 7, 0) #include "../qmuparser/qmudef.h" +#include "../vganalytics/vganalytics.h" #include #include @@ -162,6 +162,9 @@ PreferencesConfigurationPage::PreferencesConfigurationPage(QWidget *parent) ui->doubleSpinBoxAcceleration->setMinimum(VCommonSettings::scrollingAccelerationMin); ui->doubleSpinBoxAcceleration->setMaximum(VCommonSettings::scrollingAccelerationMax); ui->doubleSpinBoxAcceleration->setValue(settings->GetScrollingAcceleration()); + + // Tab Privacy + ui->checkBoxSendUsageStatistics->setChecked(settings->IsCollectStatistic()); } //--------------------------------------------------------------------------------------------------------------------- @@ -254,6 +257,10 @@ auto PreferencesConfigurationPage::Apply() -> QStringList settings->SetWheelMouseScale(ui->doubleSpinBoxWheel->value()); settings->SetScrollingAcceleration(ui->doubleSpinBoxAcceleration->value()); + // Tab Privacy + settings->SetCollectStatistic(ui->checkBoxSendUsageStatistics->isChecked()); + VGAnalytics::Instance()->Enable(ui->checkBoxSendUsageStatistics->isChecked()); + return preferences; } diff --git a/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.ui b/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.ui index 0ae06b7f4..c2b063fcc 100644 --- a/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.ui +++ b/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.ui @@ -527,6 +527,43 @@ + + + Privacy + + + + + + Send usage statistics + + + + + + + Please help to improve Valentina's quality by automatically sending usage statistics. Sent data contains <span style=" font-weight:700;">no potentially sensitive information</span> like user names, email addresses, file contents or file paths. + + + true + + + + + + + Qt::Vertical + + + + 20 + 716 + + + + + + diff --git a/src/app/valentina/mainwindow.cpp b/src/app/valentina/mainwindow.cpp index 627e05426..f2b419c04 100644 --- a/src/app/valentina/mainwindow.cpp +++ b/src/app/valentina/mainwindow.cpp @@ -38,9 +38,11 @@ #include "../ifc/xml/vvstconverter.h" #include "../vformat/vmeasurements.h" #include "../vformat/vpatternrecipe.h" +#include "../vganalytics/vganalytics.h" #include "../vlayout/dialogs/watermarkwindow.h" #include "../vmisc/customevents.h" #include "../vmisc/def.h" +#include "../vmisc/dialogs/dialogaskcollectstatistic.h" #include "../vmisc/qxtcsvmodel.h" #include "../vmisc/vmodifierkey.h" #include "../vmisc/vsysexits.h" @@ -505,7 +507,7 @@ MainWindow::MainWindow(QWidget *parent) if (VApplication::IsGUIMode()) { - QTimer::singleShot(V_SECONDS(1), this, &MainWindow::SetDefaultGUILanguage); + QTimer::singleShot(V_SECONDS(1), this, &MainWindow::AskDefaultSettings); } ui->actionExportFontCorrections->setEnabled(settings->GetSingleStrokeOutlineFont()); @@ -4548,7 +4550,7 @@ void MainWindow::ClearPatternMessages() } //--------------------------------------------------------------------------------------------------------------------- -void MainWindow::SetDefaultGUILanguage() +void MainWindow::AskDefaultSettings() { if (VApplication::IsGUIMode()) { @@ -4565,6 +4567,38 @@ void MainWindow::SetDefaultGUILanguage() VAbstractApplication::VApp()->LoadTranslation(locale); } } + + if (settings->IsAskCollectStatistic()) + { + DialogAskCollectStatistic dialog(this); + if (dialog.exec() == QDialog::Accepted) + { + settings->SetCollectStatistic(dialog.CollectStatistic()); + } + + settings->SetAskCollectStatistic(false); + } + + if (settings->IsCollectStatistic()) + { + auto *statistic = VGAnalytics::Instance(); + statistic->SetGUILanguage(settings->GetLocale()); + + QString clientID = settings->GetClientID(); + bool freshID = false; + if (clientID.isEmpty()) + { + clientID = QUuid::createUuid().toString(); + settings->SetClientID(clientID); + statistic->SetClientID(clientID); + freshID = true; + } + + statistic->Enable(true); + + const qint64 uptime = VAbstractValApplication::VApp()->AppUptime(); + freshID ? statistic->SendAppFreshInstallEvent(uptime) : statistic->SendAppStartEvent(uptime); + } } } diff --git a/src/app/valentina/mainwindow.h b/src/app/valentina/mainwindow.h index de3fc6ad7..00fc3024b 100644 --- a/src/app/valentina/mainwindow.h +++ b/src/app/valentina/mainwindow.h @@ -237,7 +237,7 @@ private slots: void ShowProgress(); void ClearPatternMessages(); - void SetDefaultGUILanguage(); + void AskDefaultSettings(); void AddBackgroundImageItem(const QUuid &id); void DeleteBackgroundImageItem(const QUuid &id); diff --git a/src/app/valentina/valentina.pro b/src/app/valentina/valentina.pro index 4fb4b8805..9a435e6ac 100644 --- a/src/app/valentina/valentina.pro +++ b/src/app/valentina/valentina.pro @@ -698,6 +698,15 @@ DEPENDPATH += $$PWD/../../libs/vdxf win32:!win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vdxf/$${DESTDIR}/vdxf.lib else:unix|win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vdxf/$${DESTDIR}/libvdxf.a +# VGAnalytics static library +unix|win32: LIBS += -L$$OUT_PWD/../../libs/vganalytics/$${DESTDIR}/ -lvganalytics + +INCLUDEPATH += $$PWD/../../libs/vganalytics +DEPENDPATH += $$PWD/../../libs/vganalytics + +win32:!win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vganalytics/$${DESTDIR}/vganalytics.lib +else:unix|win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vganalytics/$${DESTDIR}/libvganalytics.a + # QMuParser library win32:CONFIG(release, debug|release): LIBS += -L$${OUT_PWD}/../../libs/qmuparser/$${DESTDIR} -lqmuparser2 else:win32:CONFIG(debug, debug|release): LIBS += -L$${OUT_PWD}/../../libs/qmuparser/$${DESTDIR} -lqmuparser2 diff --git a/src/app/valentina/valentina.qbs b/src/app/valentina/valentina.qbs index 1c5ab6a2b..5c000376c 100644 --- a/src/app/valentina/valentina.qbs +++ b/src/app/valentina/valentina.qbs @@ -14,6 +14,7 @@ VToolApp { Depends { name: "VToolsLib"; } Depends { name: "VFormatLib"; } Depends { name: "VMiscLib"; } + Depends { name: "VGAnalyticsLib" } Depends { name: "Qt.xmlpatterns" diff --git a/src/libs/libs.pri b/src/libs/libs.pri index 322920d57..46a291726 100644 --- a/src/libs/libs.pri +++ b/src/libs/libs.pri @@ -33,3 +33,6 @@ INCLUDEPATH += $${PWD}/vpropertyexplorer #VTools static library INCLUDEPATH += $${PWD}/vtest + +#VGAnalytics static library +INCLUDEPATH += $${PWD}/vganalytics diff --git a/src/libs/libs.pro b/src/libs/libs.pro index 84cbd2f09..aad3bde72 100644 --- a/src/libs/libs.pro +++ b/src/libs/libs.pro @@ -13,4 +13,5 @@ SUBDIRS = \ vtools \ vformat \ fervor \ - vtest + vtest \ + vganalytics diff --git a/src/libs/libs.qbs b/src/libs/libs.qbs index c9d277dff..a15fba06a 100644 --- a/src/libs/libs.qbs +++ b/src/libs/libs.qbs @@ -14,5 +14,6 @@ Project { "vtest/vtest.qbs", "vtools/vtools.qbs", "vwidgets/vwidgets.qbs", + "vganalytics/vganalytics.qbs", ] } diff --git a/src/libs/vganalytics/def.h b/src/libs/vganalytics/def.h new file mode 100644 index 000000000..7f8aba7aa --- /dev/null +++ b/src/libs/vganalytics/def.h @@ -0,0 +1,36 @@ +/************************************************************************ + ** + ** @file def.h + ** @author Roman Telezhynskyi + ** @date 26 6, 2023 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2023 Valentina project + ** All Rights Reserved. + ** + ** Valentina is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with Valentina. If not, see . + ** + *************************************************************************/ +#ifndef GADEF_H +#define GADEF_H + +#include + +const QString GA_MEASUREMENT_ID = QStringLiteral("G-KH8CCG06VJ"); // NOLINT(clazy-non-pod-global-static) +const QString GA_API_SECRET = QStringLiteral("vnXM9BlCQXC9nvaXid_lAA"); // NOLINT(clazy-non-pod-global-static) + +#endif // GADEF_H diff --git a/src/libs/vganalytics/vganalytics.cpp b/src/libs/vganalytics/vganalytics.cpp new file mode 100644 index 000000000..64ea6b2d7 --- /dev/null +++ b/src/libs/vganalytics/vganalytics.cpp @@ -0,0 +1,306 @@ +/************************************************************************ + ** + ** @file vganalytics.cpp + ** @author Roman Telezhynskyi + ** @date 26 6, 2023 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2023 Valentina project + ** All Rights Reserved. + ** + ** Valentina is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with Valentina. If not, see . + ** + *************************************************************************/ +#include "vganalytics.h" +#include "qguiapplication.h" +#include "qstringliteral.h" +#include "vganalyticsworker.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------------------------------------------------------- +VGAnalytics::VGAnalytics(QObject *parent) + : QObject{parent}, + d(new VGAnalyticsWorker(this)) +{ +} + +//--------------------------------------------------------------------------------------------------------------------- +VGAnalytics::~VGAnalytics() +{ + delete d; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VGAnalytics::Instance() -> VGAnalytics * +{ + static VGAnalytics *instance = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + if (instance == nullptr) + { + instance = new VGAnalytics(); + } + + return instance; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VGAnalytics::SetLogLevel(enum VGAnalytics::LogLevel logLevel) +{ + d->m_logLevel = logLevel; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VGAnalytics::LogLevel() const -> enum VGAnalytics::LogLevel { return d->m_logLevel; } + +//--------------------------------------------------------------------------------------------------------------------- +void VGAnalytics::SetRepoRevision(const QString &rev) +{ + d->m_repoRevision = rev; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VGAnalytics::RepoRevision() const -> QString +{ + return d->m_repoRevision; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VGAnalytics::SetGUILanguage(const QString &language) +{ + d->m_guiLanguage = language.toLower().replace(QChar('_'), QChar('-')); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VGAnalytics::GUILanguage() const -> QString +{ + return d->m_guiLanguage; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VGAnalytics::SetSendInterval(int milliseconds) +{ + d->m_timer.setInterval(milliseconds); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VGAnalytics::SendInterval() const -> int +{ + return (d->m_timer.interval()); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VGAnalytics::IsEnabled() -> bool +{ + return d->m_isEnabled; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VGAnalytics::SetMeasurementId(const QString &measurementId) +{ + d->m_measurementId = measurementId; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VGAnalytics::SetApiSecret(const QString &apiSecret) +{ + d->m_apiSecret = apiSecret; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VGAnalytics::SetClientID(const QString &clientID) +{ + d->m_clientID = clientID; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VGAnalytics::Enable(bool state) +{ + d->Enable(state); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VGAnalytics::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; + } +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VGAnalytics::NetworkAccessManager() const -> QNetworkAccessManager * +{ + return d->networkManager; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VGAnalytics::SendAppFreshInstallEvent(qint64 engagementTimeMsec) +{ + SendEvent(QStringLiteral("vapp_fresh_install"), InitAppStartEventParams(engagementTimeMsec)); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VGAnalytics::SendAppStartEvent(qint64 engagementTimeMsec) +{ + SendEvent(QStringLiteral("vapp_start"), InitAppStartEventParams(engagementTimeMsec)); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VGAnalytics::SendAppCloseEvent(qint64 engagementTimeMsec) +{ + QHash params{ + // {QStringLiteral("category"), ""}, + // In order for user activity to display in standard reports like Realtime, engagement_time_msec and session_id + // must be supplied as part of the params for an event. + // https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag#optional_parameters_for_reports + {QStringLiteral("engagement_time_msec"), engagementTimeMsec}, + }; + + SendEvent(QStringLiteral("vapp_close"), params); + QNetworkReply *reply = d->PostMessage(); + if (reply) + { + QTimer timer; + const int timeoutSeconds = 3; // Wait for 3 seconds + timer.setSingleShot(true); + timer.start(timeoutSeconds * 1000); + + QEventLoop loop; + connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); + QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + loop.exec(); // wait for finished + } +} + +//--------------------------------------------------------------------------------------------------------------------- +/** + * A query for a POST message will be created to report this event. The + * created query will be stored in a message queue. + */ +void VGAnalytics::SendEvent(const QString &eventName, const QHash ¶ms) +{ + /* + // event body example + { + "client_id": "XXXXXXXXXX.YYYYYYYYYY", + "events": [{ + "name": "refund", + "params": { + "currency": "USD", + "value": "9.99", + "transaction_id": "ABC-123" + } + }] + } + */ + + QJsonObject root; + root[QStringLiteral("client_id")] = d->m_clientID; + + QJsonObject event; + event[QStringLiteral("name")] = eventName; + + QJsonObject eventParams; + + auto i = params.constBegin(); + while (i != params.constEnd()) + { + eventParams[i.key()] = i.value(); + ++i; + } + + // In order for user activity to display in standard reports like Realtime, engagement_time_msec and session_id must + // be supplied as part of the params for an event. + // https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag#optional_parameters_for_reports + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) + eventParams[QStringLiteral("session_id")] = qApp->sessionId(); + event[QStringLiteral("params")] = eventParams; + + QJsonArray eventArray; + eventArray.append(event); + root[QStringLiteral("events")] = eventArray; + + d->EnqueQueryWithCurrentTime(root); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VGAnalytics::InitAppStartEventParams(qint64 engagementTimeMsec) const -> QHash +{ + + QHash params{ + // {QStringLiteral("category"), ""}, + {QStringLiteral("qt_version"), QT_VERSION_STR}, + {QStringLiteral("app_version"), d->m_appVersion}, + {QStringLiteral("word_size"), QStringLiteral("%1 bit").arg(QSysInfo::WordSize)}, + {QStringLiteral("cpu_architecture"), QSysInfo::currentCpuArchitecture()}, + {QStringLiteral("revision"), d->m_repoRevision}, + {QStringLiteral("os_version"), QSysInfo::prettyProductName()}, + {QStringLiteral("screen_size"), d->m_screenResolution}, + {QStringLiteral("screen_scale_factor"), d->m_screenScaleFactor}, + // In order for user activity to display in standard reports like Realtime, engagement_time_msec and session_id + // must be supplied as part of the params for an event. + // https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag#optional_parameters_for_reports + {QStringLiteral("engagement_time_msec"), engagementTimeMsec}, + {QStringLiteral("gui_language"), d->m_guiLanguage}, + }; + return params; +} + +//--------------------------------------------------------------------------------------------------------------------- +/** + * Qut stream to persist class GAnalytics. + */ +auto operator<<(QDataStream &outStream, const VGAnalytics &analytics) -> QDataStream & +{ + outStream << analytics.d->PersistMessageQueue(); + + return outStream; +} + +//--------------------------------------------------------------------------------------------------------------------- +/** + * In stream to read GAnalytics from file. + */ +auto operator>>(QDataStream &inStream, VGAnalytics &analytics) -> QDataStream & +{ + QList dataList; + inStream >> dataList; + analytics.d->ReadMessagesFromFile(dataList); + + return inStream; +} diff --git a/src/libs/vganalytics/vganalytics.h b/src/libs/vganalytics/vganalytics.h new file mode 100644 index 000000000..802e265ee --- /dev/null +++ b/src/libs/vganalytics/vganalytics.h @@ -0,0 +1,99 @@ +/************************************************************************ + ** + ** @file vganalytics.h + ** @author Roman Telezhynskyi + ** @date 26 6, 2023 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2023 Valentina project + ** All Rights Reserved. + ** + ** Valentina is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with Valentina. If not, see . + ** + *************************************************************************/ +#ifndef VGANALYTICS_H +#define VGANALYTICS_H + +#include + +class QNetworkAccessManager; +class VGAnalyticsWorker; + +// https://developers.google.com/analytics/devguides/collection/protocol/ga4/verify-implementation?client_type=gtag +class VGAnalytics : public QObject +{ + Q_OBJECT // NOLINT + +public: + ~VGAnalytics() override; + + static auto Instance() -> VGAnalytics *; + + enum LogLevel + { + Debug, + Info, + Error + }; + + Q_ENUM(LogLevel) // NOLINT + + void SetMeasurementId(const QString &measurementId); + void SetApiSecret(const QString &apiSecret); + void SetClientID(const QString &clientID); + + void SetLogLevel(LogLevel logLevel); + auto LogLevel() const -> LogLevel; + + void SetRepoRevision(const QString &rev); + auto RepoRevision() const -> QString; + + void SetGUILanguage(const QString &language); + auto GUILanguage() const -> QString; + + void SetSendInterval(int milliseconds); + auto SendInterval() const -> int; + + void Enable(bool state = true); + auto IsEnabled() -> bool; + + /// Get or set the network access manager. If none is set, the class creates its own on the first request + void SetNetworkAccessManager(QNetworkAccessManager *networkAccessManager); + auto NetworkAccessManager() const -> QNetworkAccessManager *; + +public slots: + void SendAppFreshInstallEvent(qint64 engagementTimeMsec); + void SendAppStartEvent(qint64 engagementTimeMsec); + void SendAppCloseEvent(qint64 engagementTimeMsec); + +private: + Q_DISABLE_COPY_MOVE(VGAnalytics) // NOLINT + + explicit VGAnalytics(QObject *parent = nullptr); + +private: + VGAnalyticsWorker *d; + + void SendEvent(const QString &eventName, const QHash ¶ms); + + auto InitAppStartEventParams(qint64 engagementTimeMsec) const -> QHash; + + friend auto operator<<(QDataStream &outStream, const VGAnalytics &analytics) -> QDataStream &; + friend auto operator>>(QDataStream &inStream, VGAnalytics &analytics) -> QDataStream &; +}; + +#endif // VGANALYTICS_H diff --git a/src/libs/vganalytics/vganalytics.pri b/src/libs/vganalytics/vganalytics.pri new file mode 100644 index 000000000..49aa55477 --- /dev/null +++ b/src/libs/vganalytics/vganalytics.pri @@ -0,0 +1,8 @@ +HEADERS += \ + $$PWD/def.h \ + $$PWD/vganalytics.h \ + $$PWD/vganalyticsworker.h + +SOURCES += \ + $$PWD/vganalytics.cpp \ + $$PWD/vganalyticsworker.cpp diff --git a/src/libs/vganalytics/vganalytics.pro b/src/libs/vganalytics/vganalytics.pro new file mode 100644 index 000000000..74c4934cd --- /dev/null +++ b/src/libs/vganalytics/vganalytics.pro @@ -0,0 +1,80 @@ +# File with common stuff for whole project +include(../../../common.pri) + +QT += network gui + +# Name of library +TARGET = vganalytics + +# We want to create a library +TEMPLATE = lib + +CONFIG += staticlib # Making static library + +# Since Q5.12 available support for C++17 +equals(QT_MAJOR_VERSION, 5):greaterThan(QT_MINOR_VERSION, 11) { + CONFIG += c++17 +} else { + CONFIG += c++14 +} + +# Use out-of-source builds (shadow builds) +CONFIG -= debug_and_release debug_and_release_target + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +# Since Qt 5.4.0 the source code location is recorded only in debug builds. +# We need this information also in release builds. For this need define QT_MESSAGELOGCONTEXT. +DEFINES += QT_MESSAGELOGCONTEXT + +include(vganalytics.pri) + +# This is static library so no need in "make install" + +# directory for executable file +DESTDIR = bin + +# files created moc +MOC_DIR = moc + +# objecs files +OBJECTS_DIR = obj + +# Set using ccache. Function enable_ccache() defined in common.pri. +$$enable_ccache() + +include(warnings.pri) + +CONFIG(debug, debug|release){ + # Debug mode +}else{ + # Release mode + !*msvc*:CONFIG += silent + DEFINES += V_NO_ASSERT + !unix:*g++*{ + QMAKE_CXXFLAGS += -fno-omit-frame-pointer # Need for exchndl.dll + } + + noDebugSymbols{ # For enable run qmake with CONFIG+=noDebugSymbols + # do nothing + } else { + !macx:!*msvc*{ + # Turn on debug symbols in release mode on Unix systems. + # On Mac OS X temporarily disabled. TODO: find way how to strip binary file. + QMAKE_CXXFLAGS_RELEASE += -g -gdwarf-3 + QMAKE_CFLAGS_RELEASE += -g -gdwarf-3 + QMAKE_LFLAGS_RELEASE = + } + } +} + +include (../libs.pri) diff --git a/src/libs/vganalytics/vganalytics.qbs b/src/libs/vganalytics/vganalytics.qbs new file mode 100644 index 000000000..c96e919d5 --- /dev/null +++ b/src/libs/vganalytics/vganalytics.qbs @@ -0,0 +1,16 @@ +VLib { + name: "VGAnalyticsLib" + files: [ + "def.h", + "vganalytics.cpp", + "vganalytics.h", + "vganalyticsworker.cpp", + "vganalyticsworker.h", + ] + Depends { name: "Qt"; submodules: ["core", "network", "gui"] } + + Export { + Depends { name: "cpp" } + cpp.includePaths: [exportingProduct.sourceDirectory] + } +} diff --git a/src/libs/vganalytics/vganalyticsworker.cpp b/src/libs/vganalytics/vganalyticsworker.cpp new file mode 100644 index 000000000..6e95c22a4 --- /dev/null +++ b/src/libs/vganalytics/vganalyticsworker.cpp @@ -0,0 +1,281 @@ +/************************************************************************ + ** + ** @file vganalyticsworker.cpp + ** @author Roman Telezhynskyi + ** @date 26 6, 2023 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2023 Valentina project + ** All Rights Reserved. + ** + ** Valentina is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with Valentina. If not, see . + ** + *************************************************************************/ +#include "vganalyticsworker.h" +#include "vganalytics.h" + +#include +#include +#include + +#include +#include + +#include +#include +#include + +const QLatin1String VGAnalyticsWorker::dateTimeFormat("yyyy,MM,dd-hh:mm::ss:zzz"); + +namespace +{ +constexpr int fourHours = 4 * 60 * 60 * 1000; +} + +//--------------------------------------------------------------------------------------------------------------------- +VGAnalyticsWorker::VGAnalyticsWorker(QObject *parent) + : QObject(parent), + m_logLevel(VGAnalytics::Error) +{ + m_appName = QCoreApplication::applicationName(); + m_appVersion = QCoreApplication::applicationVersion(); + m_request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json; charset=utf-8"); + m_request.setHeader(QNetworkRequest::UserAgentHeader, GetUserAgent()); + + m_guiLanguage = QLocale::system().name().toLower().replace(QChar('_'), QChar('-')); + + QScreen *screen = QGuiApplication::primaryScreen(); + QSize size = screen->size(); + + m_screenResolution = QStringLiteral("%1x%2").arg(size.width()).arg(size.height()); + m_screenScaleFactor = screen->logicalDotsPerInchX() / 96.0; + + m_timer.setInterval(m_timerInterval); + connect(&m_timer, &QTimer::timeout, this, &VGAnalyticsWorker::PostMessage); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VGAnalyticsWorker::Enable(bool state) +{ + // state change to the same is not valid. + if (m_isEnabled == state) + { + return; + } + + m_isEnabled = state; + if (m_isEnabled) + { + // enable -> start doing things :) + m_timer.start(); + } + else + { + // disable -> stop the timer + m_timer.stop(); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VGAnalyticsWorker::LogMessage(enum VGAnalytics::LogLevel level, const QString &message) const +{ + if (m_logLevel > level) + { + return; + } + + qDebug() << "[Analytics]" << message; +} + +//--------------------------------------------------------------------------------------------------------------------- +/** + * 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. + */ +auto VGAnalyticsWorker::GetUserAgent() const -> QString +{ + return QStringLiteral("%1/%2").arg(m_appName, m_appVersion); +} + +//--------------------------------------------------------------------------------------------------------------------- +/** + * 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. + */ +auto VGAnalyticsWorker::PersistMessageQueue() -> QList +{ + QList dataList; + dataList.reserve(m_messageQueue.size() * 2); + + for (const auto &buffer : m_messageQueue) + { + dataList << QJsonDocument(buffer.postQuery).toJson(); + 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 VGAnalyticsWorker::ReadMessagesFromFile(const QList &dataList) +{ + QListIterator iter(dataList); + while (iter.hasNext()) + { + QString queryString = iter.next(); + QString dateString = iter.next(); + QDateTime dateTime = QDateTime::fromString(dateString, dateTimeFormat); + QueryBuffer buffer; + + QJsonDocument jsonDocument = QJsonDocument::fromJson(queryString.toUtf8()); + if (jsonDocument.isNull()) + { + qDebug() << "===> please check the string " << queryString.toUtf8(); + } + QJsonObject jsonObject = jsonDocument.object(); + + buffer.postQuery = jsonObject; + 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 VGAnalyticsWorker::EnqueQueryWithCurrentTime(const QJsonObject &query) +{ + QueryBuffer buffer; + buffer.postQuery = query; + buffer.time = QDateTime::currentDateTime(); + + m_messageQueue.enqueue(buffer); +} + +//--------------------------------------------------------------------------------------------------------------------- +/** + * 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. + */ +auto VGAnalyticsWorker::PostMessage() -> QNetworkReply * +{ + if (m_messageQueue.isEmpty()) + { + // queue empty -> try sending later + m_timer.start(); + return nullptr; + } + + // queue has messages -> stop timer and start sending + m_timer.stop(); + + QString connection = QStringLiteral("close"); + if (m_messageQueue.count() > 1) + { + connection = QLatin1String("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(); + return PostMessage(); + } + + QByteArray requestJson = QJsonDocument(buffer.postQuery).toJson(QJsonDocument::Compact); + m_request.setRawHeader("Connection", connection.toUtf8()); + m_request.setHeader(QNetworkRequest::ContentLengthHeader, requestJson.length()); + + if (m_measurementId.isEmpty()) + { + LogMessage(VGAnalytics::Error, QStringLiteral("google analytics measurement id was not set!")); + } + if (m_apiSecret.isEmpty()) + { + LogMessage(VGAnalytics::Error, QStringLiteral("google analytics api seceret was not set!")); + } + + QString requestUrl = QStringLiteral("https://www.google-analytics.com/mp/collect?measurement_id=%1&api_secret=%2"); + requestUrl = requestUrl.arg(m_measurementId, m_apiSecret); + m_request.setUrl(QUrl(requestUrl)); + + LogMessage(VGAnalytics::Debug, "GA Query string = " + requestJson); + + // Create a new network access manager if we don't have one yet + if (networkManager == nullptr) + { + networkManager = new QNetworkAccessManager(this); + } + + QNetworkReply *reply = networkManager->post(m_request, requestJson); + connect(reply, &QNetworkReply::finished, this, &VGAnalyticsWorker::PostMessageFinished); + return reply; +} + +//--------------------------------------------------------------------------------------------------------------------- +/** + * 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 VGAnalyticsWorker::PostMessageFinished() +{ + auto *reply = qobject_cast(sender()); + + int httpStausCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (httpStausCode < 200 || httpStausCode > 299) + { + LogMessage(VGAnalytics::Error, QStringLiteral("Error posting message: %1").arg(reply->errorString())); + + // An error ocurred. Try sending later. + m_timer.start(); + return; + } + + LogMessage(VGAnalytics::Debug, QStringLiteral("Message sent")); + + m_messageQueue.dequeue(); + PostMessage(); + reply->deleteLater(); +} diff --git a/src/libs/vganalytics/vganalyticsworker.h b/src/libs/vganalytics/vganalyticsworker.h new file mode 100644 index 000000000..4708984e4 --- /dev/null +++ b/src/libs/vganalytics/vganalyticsworker.h @@ -0,0 +1,97 @@ +/************************************************************************ + ** + ** @file vganalyticsworker.h + ** @author Roman Telezhynskyi + ** @date 26 6, 2023 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2023 Valentina project + ** All Rights Reserved. + ** + ** Valentina is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with Valentina. If not, see . + ** + *************************************************************************/ +#ifndef VGANALYTICSWORKER_H +#define VGANALYTICSWORKER_H + +#include "vganalytics.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +class QNetworkReply; + +struct QueryBuffer +{ + QJsonObject postQuery{}; + QDateTime time{}; +}; + +class VGAnalyticsWorker : public QObject +{ + Q_OBJECT // NOLINT + +public: + explicit VGAnalyticsWorker(QObject *parent = nullptr); + + QNetworkAccessManager *networkManager{nullptr}; // NOLINT(misc-non-private-member-variables-in-classes) + + QQueue m_messageQueue{}; // NOLINT(misc-non-private-member-variables-in-classes) + QTimer m_timer{}; // NOLINT(misc-non-private-member-variables-in-classes) + QNetworkRequest m_request{}; // NOLINT(misc-non-private-member-variables-in-classes) + enum VGAnalytics::LogLevel m_logLevel{VGAnalytics::Debug}; // NOLINT(misc-non-private-member-variables-in-classes) + + QString m_measurementId{}; // NOLINT(misc-non-private-member-variables-in-classes) + QString m_apiSecret{}; // NOLINT(misc-non-private-member-variables-in-classes) + QString m_clientID{}; // NOLINT(misc-non-private-member-variables-in-classes) + QString m_userID{}; // NOLINT(misc-non-private-member-variables-in-classes) + QString m_appName{}; // NOLINT(misc-non-private-member-variables-in-classes) + QString m_appVersion{}; // NOLINT(misc-non-private-member-variables-in-classes) + QString m_guiLanguage{}; // NOLINT(misc-non-private-member-variables-in-classes) + QString m_screenResolution{}; // NOLINT(misc-non-private-member-variables-in-classes) + QString m_repoRevision{}; // NOLINT(misc-non-private-member-variables-in-classes) + + bool m_isEnabled{false}; // NOLINT(misc-non-private-member-variables-in-classes) + int m_timerInterval{30000}; // NOLINT(misc-non-private-member-variables-in-classes) + qreal m_screenScaleFactor{}; // NOLINT(misc-non-private-member-variables-in-classes) + + const static QLatin1String dateTimeFormat; + + void LogMessage(enum VGAnalytics::LogLevel level, const QString &message) const; + + auto GetUserAgent() const -> QString; + auto PersistMessageQueue() -> QList; + void ReadMessagesFromFile(const QList &dataList); + + void EnqueQueryWithCurrentTime(const QJsonObject &query); + void Enable(bool state); + +public slots: + QNetworkReply *PostMessage(); // NOLINT(modernize-use-trailing-return-type) + void PostMessageFinished(); + +private: + Q_DISABLE_COPY_MOVE(VGAnalyticsWorker) +}; + +#endif // VGANALYTICSWORKER_H diff --git a/src/libs/vganalytics/warnings.pri b/src/libs/vganalytics/warnings.pri new file mode 100644 index 000000000..24184cea5 --- /dev/null +++ b/src/libs/vganalytics/warnings.pri @@ -0,0 +1,82 @@ +#Turn on compilers warnings. +unix { + *g++*{ + QMAKE_CXXFLAGS += \ + # Key -isystem disable checking errors in system headers. + -isystem "$${OUT_PWD}/$${MOC_DIR}" \ + -isystem "$${OUT_PWD}/$${UI_DIR}" \ + $$GCC_DEBUG_CXXFLAGS # See common.pri for more details. + + checkWarnings{ # For enable run qmake with CONFIG+=checkWarnings + QMAKE_CXXFLAGS += -Werror + } + + noAddressSanitizer{ # For enable run qmake with CONFIG+=noAddressSanitizer + # do nothing + } else { + CONFIG(debug, debug|release){ + # Debug mode + #gcc’s 4.8.0 Address Sanitizer + #http://blog.qt.digia.com/blog/2013/04/17/using-gccs-4-8-0-address-sanitizer-with-qt/ + QMAKE_CXXFLAGS += -fsanitize=address -fno-omit-frame-pointer + QMAKE_CFLAGS += -fsanitize=address -fno-omit-frame-pointer + QMAKE_LFLAGS += -fsanitize=address + } + } + + gccUbsan{ # For enable run qmake with CONFIG+=gccUbsan + CONFIG(debug, debug|release){ + # Debug mode + #gcc’s 4.9.0 Undefined Behavior Sanitizer (ubsan) + QMAKE_CXXFLAGS += -fsanitize=undefined + QMAKE_CFLAGS += -fsanitize=undefined + QMAKE_LFLAGS += -fsanitize=undefined + } + } + } + + *clang*{ + QMAKE_CXXFLAGS += \ + # Key -isystem disable checking errors in system headers. + -isystem "$${OUT_PWD}/$${MOC_DIR}" \ + -isystem "$${OUT_PWD}/$${UI_DIR}" \ + $$CLANG_DEBUG_CXXFLAGS # See common.pri for more details. + + checkWarnings{ # For enable run qmake with CONFIG+=checkWarnings + QMAKE_CXXFLAGS += -Werror + } + + # -isystem key works only for headers. In some cases it's not enough. But we can't delete these warnings and + # want them in global list. Compromise decision delete them from local list. + QMAKE_CXXFLAGS -= \ + -Wmissing-prototypes \ + -Wundefined-reinterpret-cast + } + + *-icc-*{ + QMAKE_CXXFLAGS += \ + -isystem "$${OUT_PWD}/$${MOC_DIR}" \ + -isystem "$${OUT_PWD}/$${UI_DIR}" \ + $$ICC_DEBUG_CXXFLAGS + + checkWarnings{ # For enable run qmake with CONFIG+=checkWarnings + QMAKE_CXXFLAGS += -Werror + } + } +} else { # Windows + *g++*{ + QMAKE_CXXFLAGS += $$GCC_DEBUG_CXXFLAGS # See common.pri for more details. + + checkWarnings{ # For enable run qmake with CONFIG+=checkWarnings + QMAKE_CXXFLAGS += -Werror + } + } + + *msvc*{ + QMAKE_CXXFLAGS += $$MSVC_DEBUG_CXXFLAGS # See common.pri for more details. + + checkWarnings{ # For enable run qmake with CONFIG+=checkWarnings + QMAKE_CXXFLAGS += -WX + } + } +} diff --git a/src/libs/vmisc/dialogs/dialogaskcollectstatistic.cpp b/src/libs/vmisc/dialogs/dialogaskcollectstatistic.cpp new file mode 100644 index 000000000..af16ce47a --- /dev/null +++ b/src/libs/vmisc/dialogs/dialogaskcollectstatistic.cpp @@ -0,0 +1,49 @@ +/************************************************************************ + ** + ** @file dialogaskcollectstatistic.cpp + ** @author Roman Telezhynskyi + ** @date 26 6, 2023 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2023 Valentina project + ** All Rights Reserved. + ** + ** Valentina is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with Valentina. If not, see . + ** + *************************************************************************/ +#include "dialogaskcollectstatistic.h" +#include "ui_dialogaskcollectstatistic.h" + +//--------------------------------------------------------------------------------------------------------------------- +DialogAskCollectStatistic::DialogAskCollectStatistic(QWidget *parent) + : QDialog(parent), + ui(new Ui::DialogAskCollectStatistic) +{ + ui->setupUi(this); +} + +//--------------------------------------------------------------------------------------------------------------------- +DialogAskCollectStatistic::~DialogAskCollectStatistic() +{ + delete ui; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto DialogAskCollectStatistic::CollectStatistic() -> bool +{ + return ui->checkBoxSendUsageStatistics->isChecked(); +} diff --git a/src/libs/vmisc/dialogs/dialogaskcollectstatistic.h b/src/libs/vmisc/dialogs/dialogaskcollectstatistic.h new file mode 100644 index 000000000..7b7520121 --- /dev/null +++ b/src/libs/vmisc/dialogs/dialogaskcollectstatistic.h @@ -0,0 +1,57 @@ +/************************************************************************ + ** + ** @file dialogaskcollectstatistic.h + ** @author Roman Telezhynskyi + ** @date 26 6, 2023 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2023 Valentina project + ** All Rights Reserved. + ** + ** Valentina is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with Valentina. If not, see . + ** + *************************************************************************/ +#ifndef DIALOGASKCOLLECTSTATISTIC_H +#define DIALOGASKCOLLECTSTATISTIC_H + +#include +#include +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) +#include "../defglobal.h" +#endif + +namespace Ui +{ +class DialogAskCollectStatistic; +} + +class DialogAskCollectStatistic : public QDialog +{ + Q_OBJECT // NOLINT + +public: + explicit DialogAskCollectStatistic(QWidget *parent = nullptr); + ~DialogAskCollectStatistic() override; + + auto CollectStatistic() -> bool; + +private: + Q_DISABLE_COPY_MOVE(DialogAskCollectStatistic) // NOLINT + Ui::DialogAskCollectStatistic *ui; +}; + +#endif // DIALOGASKCOLLECTSTATISTIC_H diff --git a/src/libs/vmisc/dialogs/dialogaskcollectstatistic.ui b/src/libs/vmisc/dialogs/dialogaskcollectstatistic.ui new file mode 100644 index 000000000..eb977b4a7 --- /dev/null +++ b/src/libs/vmisc/dialogs/dialogaskcollectstatistic.ui @@ -0,0 +1,103 @@ + + + DialogAskCollectStatistic + + + + 0 + 0 + 376 + 183 + + + + Privacy + + + + :/icon/64x64/icon64x64.png:/icon/64x64/icon64x64.png + + + + + + Send usage statistics + + + true + + + + + + + Please help to improve Valentina's quality by automatically sending usage statistics. Sent data contains <span style=" font-weight:700;">no potentially sensitive information</span> like user names, email addresses, file contents or file paths. + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + DialogAskCollectStatistic + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DialogAskCollectStatistic + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/libs/vmisc/vabstractapplication.cpp b/src/libs/vmisc/vabstractapplication.cpp index 9a11f8ae4..4046ba30b 100644 --- a/src/libs/vmisc/vabstractapplication.cpp +++ b/src/libs/vmisc/vabstractapplication.cpp @@ -153,6 +153,8 @@ VAbstractApplication::VAbstractApplication(int &argc, char **argv) #endif connect(this, &QApplication::aboutToQuit, this, &VAbstractApplication::AboutToQuit); + + m_uptimeTimer.start(); } //--------------------------------------------------------------------------------------------------------------------- @@ -433,6 +435,12 @@ void VAbstractApplication::RestartSVGFontDatabaseWatcher() } } +//--------------------------------------------------------------------------------------------------------------------- +auto VAbstractApplication::AppUptime() const -> qint64 +{ + return m_uptimeTimer.elapsed(); +} + #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) //--------------------------------------------------------------------------------------------------------------------- auto VAbstractApplication::TextCodecCache(QStringConverter::Encoding encoding) const -> VTextCodec * diff --git a/src/libs/vmisc/vabstractapplication.h b/src/libs/vmisc/vabstractapplication.h index c9b79c50a..cdf1e5eb7 100644 --- a/src/libs/vmisc/vabstractapplication.h +++ b/src/libs/vmisc/vabstractapplication.h @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -104,6 +105,8 @@ public: auto SVGFontDatabase() -> VSvgFontDatabase *; void RestartSVGFontDatabaseWatcher(); + auto AppUptime() const -> qint64; + protected: QUndoStack *undoStack; @@ -120,6 +123,8 @@ protected: QPointer appTranslator{nullptr}; QPointer pmsTranslator{nullptr}; + QElapsedTimer m_uptimeTimer{}; + virtual void InitTrVars() = 0; static void CheckSystemLocale(); diff --git a/src/libs/vmisc/vcommonsettings.cpp b/src/libs/vmisc/vcommonsettings.cpp index cff496e98..22fcf09b1 100644 --- a/src/libs/vmisc/vcommonsettings.cpp +++ b/src/libs/vmisc/vcommonsettings.cpp @@ -233,6 +233,10 @@ Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingWatermarkEditorSize, (QLatin1Str // NOLINTNEXTLINE Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingWatermarkCustomColors, (QLatin1String("watermarkCustomColors"))) +Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingsStatistictAskCollect, (QLatin1String("askCollect"))) // NOLINT +Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingsStatistictCollect, (QLatin1String("collect"))) // NOLINT +Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingsStatistictClientID, (QLatin1String("clientID"))) // NOLINT + // Reading settings file is very expensive, cache curve approximation to speed up getting value qreal curveApproximationCached = -1; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) Q_GLOBAL_STATIC(QString, localeCached) // NOLINT @@ -1116,20 +1120,20 @@ auto VCommonSettings::GetCSVSeparator() const -> QChar switch (separator) { case 0: - return QChar('\t'); + return QChar('\t'); // NOLINT(modernize-return-braced-init-list) case 1: - return QChar(';'); + return QChar(';'); // NOLINT(modernize-return-braced-init-list) case 2: - return QChar(' '); + return QChar(' '); // NOLINT(modernize-return-braced-init-list) default: - return QChar(','); + return QChar(','); // NOLINT(modernize-return-braced-init-list) } } //--------------------------------------------------------------------------------------------------------------------- auto VCommonSettings::GetDefCSVSeparator() -> QChar { - return QChar(','); + return QChar(','); // NOLINT(modernize-return-braced-init-list) } //--------------------------------------------------------------------------------------------------------------------- @@ -1751,6 +1755,51 @@ void VCommonSettings::SetWatermarkCustomColors(QVector colors) settings.sync(); } +//--------------------------------------------------------------------------------------------------------------------- +auto VCommonSettings::IsAskCollectStatistic() const -> bool +{ + QSettings settings(this->format(), this->scope(), this->organizationName(), *commonIniFilename); + return settings.value(*settingsStatistictAskCollect, 1).toBool(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VCommonSettings::SetAskCollectStatistic(bool value) +{ + QSettings settings(this->format(), this->scope(), this->organizationName(), *commonIniFilename); + settings.setValue(*settingsStatistictAskCollect, value); + settings.sync(); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VCommonSettings::IsCollectStatistic() const -> bool +{ + QSettings settings(this->format(), this->scope(), this->organizationName(), *commonIniFilename); + return settings.value(*settingsStatistictCollect, 1).toBool(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VCommonSettings::SetCollectStatistic(bool value) +{ + QSettings settings(this->format(), this->scope(), this->organizationName(), *commonIniFilename); + settings.setValue(*settingsStatistictCollect, value); + settings.sync(); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VCommonSettings::GetClientID() const -> QString +{ + QSettings settings(this->format(), this->scope(), this->organizationName(), *commonIniFilename); + return settings.value(*settingsStatistictClientID, QString()).toString(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VCommonSettings::SetClientID(const QString &clientID) +{ + QSettings settings(this->format(), this->scope(), this->organizationName(), *commonIniFilename); + settings.setValue(*settingsStatistictClientID, clientID); + settings.sync(); +} + //--------------------------------------------------------------------------------------------------------------------- auto VCommonSettings::PrepareStandardFiles(const QString ¤tPath, const QString &standardPath, const QString &defPath) -> QString diff --git a/src/libs/vmisc/vcommonsettings.h b/src/libs/vmisc/vcommonsettings.h index d93d1f4c3..67a06322c 100644 --- a/src/libs/vmisc/vcommonsettings.h +++ b/src/libs/vmisc/vcommonsettings.h @@ -321,6 +321,15 @@ public: auto GetWatermarkCustomColors() const -> QVector; void SetWatermarkCustomColors(QVector colors); + auto IsAskCollectStatistic() const -> bool; + void SetAskCollectStatistic(bool value); + + auto IsCollectStatistic() const -> bool; + void SetCollectStatistic(bool value); + + auto GetClientID() const -> QString; + void SetClientID(const QString &clientID); + signals: void SVGFontsPathChanged(const QString &oldPath, const QString &newPath); diff --git a/src/libs/vmisc/vmisc.pri b/src/libs/vmisc/vmisc.pri index 0db34ea68..1e04cacc5 100644 --- a/src/libs/vmisc/vmisc.pri +++ b/src/libs/vmisc/vmisc.pri @@ -3,6 +3,7 @@ SOURCES += \ $$PWD/def.cpp \ + $$PWD/dialogs/dialogaskcollectstatistic.cpp \ $$PWD/svgfont/svgdef.cpp \ $$PWD/svgfont/vsvgfont.cpp \ $$PWD/svgfont/vsvgfontdatabase.cpp \ @@ -61,6 +62,7 @@ HEADERS += \ $$PWD/bpstd/utility.hpp \ $$PWD/bpstd/variant.hpp \ $$PWD/compatibility.h \ + $$PWD/dialogs/dialogaskcollectstatistic.h \ $$PWD/lambdaconstants.h \ $$PWD/stable.h \ $$PWD/def.h \ @@ -122,5 +124,6 @@ contains(QT_VERSION, ^5\\.[0-2]\\.[0-2]$) { # Since Qt 5.3.0 } FORMS += \ + $$PWD/dialogs/dialogaskcollectstatistic.ui \ $$PWD/dialogs/dialogexporttocsv.ui \ $$PWD/dialogs/dialogselectlanguage.ui diff --git a/src/libs/vmisc/vmisc.qbs b/src/libs/vmisc/vmisc.qbs index 6c0487667..9f125aa3b 100644 --- a/src/libs/vmisc/vmisc.qbs +++ b/src/libs/vmisc/vmisc.qbs @@ -67,12 +67,15 @@ VLib { name: "dialogs" prefix: "dialogs/" files: [ + "dialogaskcollectstatistic.cpp", + "dialogaskcollectstatistic.h", + "dialogaskcollectstatistic.ui", "dialogexporttocsv.cpp", "dialogselectlanguage.cpp", "dialogexporttocsv.h", "dialogselectlanguage.h", "dialogexporttocsv.ui", - "dialogselectlanguage.ui" + "dialogselectlanguage.ui", ] }