From 95ce718d41d117b2f29696b02baeda715c944071 Mon Sep 17 00:00:00 2001 From: Floris Bos Date: Thu, 26 Nov 2020 22:26:15 +0100 Subject: [PATCH] Enable telemetry - Phone back home image downloaded for image popularity research. Only in case image comes from our repository (NOT for custom images) --- CMakeLists.txt | 4 +-- config.h | 8 +++-- debian/changelog | 2 ++ downloadstatstelemetry.cpp | 53 ++++++++++++++++++++++++++++++++ downloadstatstelemetry.h | 31 +++++++++++++++++++ imagewriter.cpp | 8 ++++- imagewriter.h | 4 +-- linux/rpi-imager.metainfo.xml.in | 4 ++- main.qml | 6 +++- 9 files changed, 111 insertions(+), 9 deletions(-) create mode 100644 downloadstatstelemetry.cpp create mode 100644 downloadstatstelemetry.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a661e3..f96d841 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ set(CMAKE_AUTORCC ON) # Adding headers explicity so they are displayed in Qt Creator set(HEADERS config.h imagewriter.h networkaccessmanagerfactory.h nan.h drivelistitem.h drivelistmodel.h drivelistmodelpollthread.h driveformatthread.h powersaveblocker.h - downloadthread.h downloadextractthread.h localfileextractthread.h dependencies/mountutils/src/mountutils.hpp) + downloadthread.h downloadextractthread.h localfileextractthread.h downloadstatstelemetry.h dependencies/mountutils/src/mountutils.hpp) # Add dependencies if (APPLE) @@ -68,7 +68,7 @@ endif() set(SOURCES "main.cpp" "imagewriter.cpp" "networkaccessmanagerfactory.cpp" "drivelistitem.cpp" "drivelistmodel.cpp" "drivelistmodelpollthread.cpp" "downloadthread.cpp" "downloadextractthread.cpp" - "driveformatthread.cpp" "localfileextractthread.cpp" "powersaveblocker.cpp" "qml.qrc") + "driveformatthread.cpp" "localfileextractthread.cpp" "powersaveblocker.cpp" "downloadstatstelemetry.cpp" "qml.qrc") find_package(Qt5 COMPONENTS Core Quick LinguistTools Svg OPTIONAL_COMPONENTS Widgets) if (Qt5Widgets_FOUND) diff --git a/config.h b/config.h index a068cfa..3d3b3c2 100644 --- a/config.h +++ b/config.h @@ -8,10 +8,14 @@ /* Repository URL */ -#define OSLIST_URL "https://downloads.raspberrypi.org/os_list_imagingutility.json" +#define OSLIST_URL "https://downloads.raspberrypi.org/os_list_imagingutility_v2.json" /* Time synchronization URL (only used on eglfs QPA platform, URL must be HTTP) */ -#define TIME_URL "http://downloads.raspberrypi.org/os_list_imagingutility.json?time_synchronization" +#define TIME_URL "http://downloads.raspberrypi.org/os_list_imagingutility_v2.json?time_synchronization" + +/* Phone home the name of images downloaded for image popularity ranking */ +#define TELEMETRY_URL "https://rpi-imager-stats.raspberrypi.org/downloads?url=$imageurl&os=$parentcategory&image=$osname" +#define TELEMETRY_ENABLED_DEFAULT true /* Hash algorithm for verifying (uncompressed image) checksum */ #define OSLIST_HASH_ALGORITHM QCryptographicHash::Sha256 diff --git a/debian/changelog b/debian/changelog index 061c309..3875fd4 100644 --- a/debian/changelog +++ b/debian/changelog @@ -8,6 +8,8 @@ rpi-imager (1.5) unstable; urgency=medium * Add update notification support * Allow translators to specify external .qm file for testing * Remove dependency on qml-module-qt-labs-settings + * Enables telemetry collecting information about which images from + repository are most popular -- Floris Bos Tue, 24 Nov 2020 10:38:21 +0100 diff --git a/downloadstatstelemetry.cpp b/downloadstatstelemetry.cpp new file mode 100644 index 0000000..cdfc4ff --- /dev/null +++ b/downloadstatstelemetry.cpp @@ -0,0 +1,53 @@ +#include "downloadstatstelemetry.h" +#include "config.h" +#include +#include +#include + +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright (C) 2020 Raspberry Pi (Trading) Limited + */ + +DownloadStatsTelemetry::DownloadStatsTelemetry(const QByteArray &url, const QByteArray &parentcategory, const QByteArray &osname, QObject *parent) + : QThread(parent) +{ + _url = QByteArray(TELEMETRY_URL).replace("$imageurl", QUrl::toPercentEncoding(url)).replace("$parentcategory", QUrl::toPercentEncoding(parentcategory)).replace("$osname", QUrl::toPercentEncoding(osname)); + _useragent = "Mozilla/5.0 rpi-imager/" IMAGER_VERSION_STR; +} + +void DownloadStatsTelemetry::run() +{ + QSettings settings; + if (!settings.value("telemetry", TELEMETRY_ENABLED_DEFAULT).toBool()) + return; + + _c = curl_easy_init(); + curl_easy_setopt(_c, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(_c, CURLOPT_WRITEFUNCTION, &DownloadStatsTelemetry::_curl_write_callback); + curl_easy_setopt(_c, CURLOPT_HEADERFUNCTION, &DownloadStatsTelemetry::_curl_header_callback); + curl_easy_setopt(_c, CURLOPT_URL, _url.constData()); + curl_easy_setopt(_c, CURLOPT_USERAGENT, _useragent.constData()); + curl_easy_setopt(_c, CURLOPT_CONNECTTIMEOUT, 10); + curl_easy_setopt(_c, CURLOPT_LOW_SPEED_TIME, 10); + curl_easy_setopt(_c, CURLOPT_LOW_SPEED_LIMIT, 10); + + CURLcode ret = curl_easy_perform(_c); + curl_easy_cleanup(_c); + + qDebug() << "Telemetry done. cURL status code =" << ret; +} + +/* /dev/null write handler */ +size_t DownloadStatsTelemetry::_curl_write_callback(char *, size_t size, size_t nmemb, void *) +{ + return size * nmemb; +} + +size_t DownloadStatsTelemetry::_curl_header_callback( void *ptr, size_t size, size_t nmemb, void *) +{ + int len = size*nmemb; + QByteArray headerstr((char *) ptr, len); + //qDebug() << "Received telemetry header:" << headerstr; + return len; +} diff --git a/downloadstatstelemetry.h b/downloadstatstelemetry.h new file mode 100644 index 0000000..7b3d974 --- /dev/null +++ b/downloadstatstelemetry.h @@ -0,0 +1,31 @@ +#ifndef DOWNLOADSTATSTELEMETRY_H +#define DOWNLOADSTATSTELEMETRY_H + +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright (C) 2020 Raspberry Pi (Trading) Limited + */ + +#include +#include +#include + +class DownloadStatsTelemetry : public QThread +{ + Q_OBJECT +public: + explicit DownloadStatsTelemetry(const QByteArray &url, const QByteArray &parentcategory, const QByteArray &osname, QObject *parent = nullptr); + +protected: + CURL *_c; + QByteArray _url, _useragent; + virtual void run(); + static size_t _curl_write_callback(char *ptr, size_t size, size_t nmemb, void *userdata); + static size_t _curl_header_callback( void *ptr, size_t size, size_t nmemb, void *userdata); + +signals: + +public slots: +}; + +#endif // DOWNLOADSTATSTELEMETRY_H diff --git a/imagewriter.cpp b/imagewriter.cpp index 97a8b33..52f669f 100644 --- a/imagewriter.cpp +++ b/imagewriter.cpp @@ -9,6 +9,7 @@ #include "dependencies/drivelist/src/drivelist.hpp" #include "driveformatthread.h" #include "localfileextractthread.h" +#include "downloadstatstelemetry.h" #include #include #include @@ -112,13 +113,15 @@ void ImageWriter::setEngine(QQmlApplicationEngine *engine) } /* Set URL to download from */ -void ImageWriter::setSrc(const QUrl &url, quint64 downloadLen, quint64 extrLen, QByteArray expectedHash, bool multifilesinzip) +void ImageWriter::setSrc(const QUrl &url, quint64 downloadLen, quint64 extrLen, QByteArray expectedHash, bool multifilesinzip, QString parentcategory, QString osname) { _src = url; _downloadLen = downloadLen; _extrLen = extrLen; _expectedHash = expectedHash; _multipleFilesInZip = multifilesinzip; + _parentCategory = parentcategory; + _osName = osname; if (!_downloadLen && url.isLocalFile()) { @@ -191,6 +194,9 @@ void ImageWriter::startWrite() else if (compressed) { _thread = new DownloadExtractThread(urlstr, _dst.toLatin1(), _expectedHash, this); + DownloadStatsTelemetry *tele = new DownloadStatsTelemetry(urlstr, _parentCategory.toLatin1(), _osName.toLatin1(), this); + connect(tele, SIGNAL(finished()), tele, SLOT(deleteLater())); + tele->start(); } else { diff --git a/imagewriter.h b/imagewriter.h index 3d17203..8108cfe 100644 --- a/imagewriter.h +++ b/imagewriter.h @@ -28,7 +28,7 @@ public: void setEngine(QQmlApplicationEngine *engine); /* Set URL to download from, and if known download length and uncompressed length */ - Q_INVOKABLE void setSrc(const QUrl &url, quint64 downloadLen = 0, quint64 extrLen = 0, QByteArray expectedHash = "", bool multifilesinzip = false); + Q_INVOKABLE void setSrc(const QUrl &url, quint64 downloadLen = 0, quint64 extrLen = 0, QByteArray expectedHash = "", bool multifilesinzip = false, QString parentcategory = "", QString osname = ""); /* Set device to write to */ Q_INVOKABLE void setDst(const QString &device, quint64 deviceSize = 0); @@ -120,7 +120,7 @@ protected slots: protected: QUrl _src, _repo; - QString _dst, _cacheFileName; + QString _dst, _cacheFileName, _parentCategory, _osName; QByteArray _expectedHash, _cachedFileHash; quint64 _downloadLen, _extrLen, _devLen, _dlnow, _verifynow; DriveListModel _drivelist; diff --git a/linux/rpi-imager.metainfo.xml.in b/linux/rpi-imager.metainfo.xml.in index 0695f9c..6b55caa 100644 --- a/linux/rpi-imager.metainfo.xml.in +++ b/linux/rpi-imager.metainfo.xml.in @@ -57,5 +57,7 @@ - + + moderate + diff --git a/main.qml b/main.qml index b042877..637d4f4 100644 --- a/main.qml +++ b/main.qml @@ -261,6 +261,7 @@ ApplicationWindow { height: parent.height-50 padding: 0 closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + property string categorySelected : "" // background of title Rectangle { @@ -936,13 +937,16 @@ ApplicationWindow { else suboslist.currentIndex = -1 osswipeview.setCurrentIndex(1) + ospopup.categorySelected = d.name } else if (typeof(d.subitems_url) == "string" && d.subitems_url !== "") { if (d.subitems_url === "internal://back") { osswipeview.setCurrentIndex(0) + ospopup.categorySelected = "" } else { + ospopup.categorySelected = d.name var suburl = d.subitems_url if (subosmodel.count>1) { @@ -994,7 +998,7 @@ ApplicationWindow { } } } else { - imageWriter.setSrc(d.url, d.image_download_size, d.extract_size, typeof(d.extract_sha256) != "undefined" ? d.extract_sha256 : "", typeof(d.contains_multiple_files) != "undefined" ? d.contains_multiple_files : false) + imageWriter.setSrc(d.url, d.image_download_size, d.extract_size, typeof(d.extract_sha256) != "undefined" ? d.extract_sha256 : "", typeof(d.contains_multiple_files) != "undefined" ? d.contains_multiple_files : false, ospopup.categorySelected, d.name) osbutton.text = d.name ospopup.close() if (imageWriter.readyToWrite()) {