From 678bd375afc9b86827d9dab1145e27aabf895ef9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Burak=20Han=C3=A7erli?= <burak.hancerli@qt.io>
Date: Mon, 26 Feb 2024 08:03:08 +0000
Subject: [PATCH] QDS-11947 Clean cached user project

---
 CMakeLists.txt                                |  13 +-
 src/backend.cpp                               | 141 ++-------------
 src/backend.h                                 |  38 +---
 src/{dsConnector.cpp => dsconnector.cpp}      |   2 +-
 src/{dsConnector.h => dsconnector.h}          |   0
 ...{projectManager.cpp => projectmanager.cpp} |  18 +-
 src/{projectManager.h => projectmanager.h}    |   1 +
 src/qrscanner.cpp                             | 163 ++++++++++++++++++
 src/qrscanner.h                               |  60 +++++++
 ...viceConnector.cpp => serviceconnector.cpp} |   2 +-
 ...{serviceConnector.h => serviceconnector.h} |   0
 ui/main.qml                                   |   2 +-
 12 files changed, 278 insertions(+), 162 deletions(-)
 rename src/{dsConnector.cpp => dsconnector.cpp} (99%)
 rename src/{dsConnector.h => dsconnector.h} (100%)
 rename src/{projectManager.cpp => projectmanager.cpp} (97%)
 rename src/{projectManager.h => projectmanager.h} (98%)
 create mode 100644 src/qrscanner.cpp
 create mode 100644 src/qrscanner.h
 rename src/{serviceConnector.cpp => serviceconnector.cpp} (99%)
 rename src/{serviceConnector.h => serviceconnector.h} (100%)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index e027714..de88c81 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -8,9 +8,13 @@ set(CMAKE_AUTOUIC ON)
 set(CMAKE_AUTOMOC ON)
 set(CMAKE_AUTORCC ON)
 
-set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD 17)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
+if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
+    add_compile_options(-Wall -Wextra)
+endif()
+
 find_package(
     QT NAMES Qt6
     COMPONENTS Core Widgets Quick Gui Qml Multimedia MultimediaWidgets Concurrent
@@ -35,9 +39,10 @@ qt_add_executable(${PROJECT_NAME}
     src/importdummy.qml
     src/main.cpp
     src/backend.cpp src/backend.h
-    src/serviceConnector.cpp src/serviceConnector.h
-    src/projectManager.cpp src/projectManager.h
-    src/dsConnector.cpp src/dsConnector.h
+    src/serviceconnector.cpp src/serviceconnector.h
+    src/projectmanager.cpp src/projectmanager.h
+    src/dsconnector.cpp src/dsconnector.h
+    src/qrscanner.cpp src/qrscanner.h
     3rdparty/zxing-cpp/example/ZXingQtReader.h
     ui/main.qml
     ui/resources.qrc
diff --git a/src/backend.cpp b/src/backend.cpp
index f403780..7a5d26d 100644
--- a/src/backend.cpp
+++ b/src/backend.cpp
@@ -24,25 +24,14 @@
 ****************************************************************************/
 
 #include "backend.h"
-#include "qapplication.h"
 
-#include <QCameraDevice>
 #include <QDesktopServices>
-#include <QElapsedTimer>
+#include <QEventLoop>
 #include <QFileInfo>
+#include <QGuiApplication>
 #include <QJsonArray>
 #include <QJsonObject>
-#include <QMediaDevices>
-#include <QMessageBox>
-#include <QPermission>
 #include <QSettings>
-#include <QSslSocket>
-#include <QVideoSink>
-#include <QtConcurrent>
-
-#include "../3rdparty/zxing-cpp/example/ZXingQtReader.h"
-
-using namespace ZXingQt;
 
 Backend::Backend(QObject *parent)
     : QObject(parent)
@@ -147,113 +136,13 @@ void Backend::updatePopup(const QString &text, bool indeterminate)
 
 void Backend::scanQrCode()
 {
-    // request permissions
-    QCameraPermission permission;
-    QCoreApplication &app = *QCoreApplication::instance();
-    switch (app.checkPermission(permission)) {
-    case Qt::PermissionStatus::Granted:
-        openCamera();
-        break;
-    case Qt::PermissionStatus::Undetermined:
-    case Qt::PermissionStatus::Denied:
-        app.requestPermission(permission, [this](const QPermission &permission) {
-            if (permission.status() == Qt::PermissionStatus::Denied) {
-                QMessageBox msgBox{QMessageBox::Critical,
-                                   "Critical:",
-                                   "Camera permission denied",
-                                   QMessageBox::Ok};
-                msgBox.exec();
-                return;
-            }
-
-            openCamera();
-        });
-        break;
-    }
-}
-
-void Backend::openCamera()
-{
-    // start camera
-    m_captureSession.reset(new QMediaCaptureSession(this));
-    m_captureSession->setCamera(new QCamera(this));
-    m_captureSession->setVideoOutput(new CustomVideoWidget);
-
-    m_captureSession->camera()->setFocusMode(QCamera::FocusModeAuto);
-
-    CustomVideoWidget *videoWidget = qobject_cast<CustomVideoWidget *>(
-        m_captureSession->videoOutput());
-
-    connect(m_captureSession->videoSink(),
-            &QVideoSink::videoFrameChanged,
-            this,
-            [&](const QVideoFrame &frame) {
-                static int i = 0;
-                static QAtomicInt running = 0;
-
-                if (i++ < 20 || running > 0) {
-                    return;
-                }
-
-                i = 0;
-
-                QtConcurrent::run([=] {
-                    // lock the thread so that only one barcode can be read at a time
-                    running++;
-
-                    QElapsedTimer timer;
-                    timer.start();
-                    QList<Result> results = ReadBarcodes(frame.toImage());
-                    qDebug() << "Barcode detection took" << timer.elapsed() << "ms";
-
-                    // release the lock so that another barcode can be read
-                    running--;
-                    return results;
-                }).then([=](const QList<Result> &results) {
-                    if (results.isEmpty() || !m_captureSession)
-                        return;
-
-                    qDebug() << "Stopping camera";
-                    qobject_cast<CustomVideoWidget *>(m_captureSession->videoOutput())->close();
-                    qDebug() << "Camera stopped";
-
-                    Result result = results.first();
-
-                    qDebug() << "Text:   " << result.text();
-                    qDebug() << "Format: " << result.format();
-                    qDebug() << "Content:" << result.contentType();
-
-                    // we have to use invokeMethod because we are in a different thread
-                    // then where the serviceConnector is created
-                    QMetaObject::invokeMethod(this,
-                                              "parseDesignViewerUrl",
-                                              Qt::QueuedConnection,
-                                              Q_ARG(QUrl, result.text()));
-                });
-            });
-
-    // stop camera when app is not active.
-    // i.e. when the user switches to another app or the screen is locked
-    QGuiApplication *app(qobject_cast<QGuiApplication *>(QCoreApplication::instance()));
-    connect(app, &QGuiApplication::applicationStateChanged, this, [&](Qt::ApplicationState state) {
-        if (state != Qt::ApplicationState::ApplicationActive && m_captureSession) {
-            qDebug() << "Application is not active. Stopping camera";
-            qobject_cast<CustomVideoWidget *>(m_captureSession->videoOutput())->close();
-        }
+    m_qrScanner.reset(new QrScanner);
+    connect(m_qrScanner.data(), &QrScanner::qrCodeScanned, this, [&](const QString &qrCode) {
+        qDebug() << "QR code scanned:" << qrCode;
+        parseDesignViewerUrl(QUrl(qrCode));
+        m_qrScanner.reset();
     });
-
-    // stop camera when video widget is closed
-    connect(videoWidget, &CustomVideoWidget::closed, this, [&] {
-        qDebug() << "Video widget closed. Clearing capture session";
-        m_captureSession->camera()->stop();
-        m_captureSession->camera()->deleteLater();
-        m_captureSession->videoOutput()->deleteLater();
-        m_captureSession.clear();
-    });
-
-    videoWidget->setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint);
-    videoWidget->show();
-    m_captureSession->camera()->start();
+    m_qrScanner->scanQrCode();
 }
 
 void Backend::initializeProjectManager()
@@ -382,9 +271,7 @@ void Backend::clearDemoCaches()
 {
     emit popupOpen();
     updatePopup("Clearing demo caches...");
-    m_projectManager.reset(new ProjectManager);
-    m_projectManager->clearDemoCaches();
-    m_projectManager.reset();
+    ProjectManager().clearDemoCaches();
     emit popupClose();
 }
 
@@ -500,6 +387,16 @@ void Backend::updateUserProjectList()
             const QString projectName{project.toObject().value("appName").toString()};
             qDebug() << "--" << projectName;
         }
+
+        // check if any project is removed on the cloud
+        for (const auto &project : m_projectList) {
+            const QString projectName{project.toObject().value("appName").toString()};
+            if (!projectList.value().contains(project)) {
+                qDebug() << "Project removed:" << projectName << ". Removing from cache...";
+                // remove the project from the cache
+                ProjectManager().clearCachedProject(project.toObject());
+            }
+        }
     }
 
     // we need to set m_projectList even if it is empty
diff --git a/src/backend.h b/src/backend.h
index a5ad992..5ce42d7 100644
--- a/src/backend.h
+++ b/src/backend.h
@@ -26,34 +26,12 @@
 #ifndef DV_ANDROID_H
 #define DV_ANDROID_H
 
-#include <QCamera>
-#include <QImageCapture>
-#include <QMediaCaptureSession>
 #include <QThread>
-#include <QVideoWidget>
-#include <QWidget>
 
-#include "dsConnector.h"
-#include "projectManager.h"
-#include "serviceConnector.h"
-
-#include <QLabel>
-#include <QVBoxLayout>
-
-class CustomVideoWidget : public QVideoWidget
-{
-    Q_OBJECT
-public:
-    explicit CustomVideoWidget(QWidget *parent = nullptr)
-        : QVideoWidget(parent)
-    {}
-    ~CustomVideoWidget() override {}
-
-    void closeEvent(QCloseEvent *event) override { emit closed(); }
-
-signals:
-    void closed();
-};
+#include "dsconnector.h"
+#include "projectmanager.h"
+#include "qrscanner.h"
+#include "serviceconnector.h"
 
 class Backend : public QObject
 {
@@ -78,13 +56,12 @@ private:
     QJsonArray m_projectList;
 
     // Other members
+
     ServiceConnector m_serviceConnector;
+    QThread m_dsConnectorThread;
     QScopedPointer<ProjectManager> m_projectManager;
     QScopedPointer<DesignStudioConnector> m_designStudioConnector;
-    QThread m_dsConnectorThread;
-
-    // QR code scanner
-    QSharedPointer<QMediaCaptureSession> m_captureSession;
+    QScopedPointer<QrScanner> m_qrScanner;
 
     // Settings
     QTimer m_backgroundTimer;
@@ -117,7 +94,6 @@ signals:
 public slots:
     QString buildInfo() const { return m_buildInfo; }
     void scanQrCode();
-    void openCamera();
 
     void runOnlineProject(const QString &url);
     void runUserProject(const QString &projectName, const QString &password);
diff --git a/src/dsConnector.cpp b/src/dsconnector.cpp
similarity index 99%
rename from src/dsConnector.cpp
rename to src/dsconnector.cpp
index 56bdd85..d82c31d 100644
--- a/src/dsConnector.cpp
+++ b/src/dsconnector.cpp
@@ -23,7 +23,7 @@
 **
 ****************************************************************************/
 
-#include "dsConnector.h"
+#include "dsconnector.h"
 
 #include <QNetworkInterface>
 
diff --git a/src/dsConnector.h b/src/dsconnector.h
similarity index 100%
rename from src/dsConnector.h
rename to src/dsconnector.h
diff --git a/src/projectManager.cpp b/src/projectmanager.cpp
similarity index 97%
rename from src/projectManager.cpp
rename to src/projectmanager.cpp
index f479b48..629536f 100644
--- a/src/projectManager.cpp
+++ b/src/projectmanager.cpp
@@ -23,7 +23,7 @@
 **
 ****************************************************************************/
 
-#include "projectManager.h"
+#include "projectmanager.h"
 
 #include <QBuffer>
 #include <QDir>
@@ -48,9 +48,9 @@
 
 ProjectManager::ProjectManager(const bool &autoScaleProject, QObject *parent)
     : QObject(parent)
-    , m_autoScaleProject(autoScaleProject)
     , m_projectCachePath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))
     , m_demoProjectCachePath(m_projectCachePath + "/demoProjects")
+    , m_autoScaleProject(autoScaleProject)
 {
     qDebug() << "ProjectManager created.";
     qDebug() << "Project cache path: " << m_projectCachePath;
@@ -399,6 +399,20 @@ bool ProjectManager::isProjectCached(const QJsonObject &projectInfo)
     return true;
 }
 
+void ProjectManager::clearCachedProject(const QJsonObject &projectInfo)
+{
+    const QString projectId = projectInfo.value("id").toString();
+    qDebug() << "Clearing cache for project " << projectId;
+
+    const QString cachePath = m_projectCachePath + "/" + projectId;
+    if (!QDir(cachePath).exists()) {
+        qDebug() << "Project " << projectId << " is not cached";
+    }
+
+    qDebug() << "Removing cache for project " << projectId;
+    QDir(cachePath).removeRecursively();
+}
+
 bool ProjectManager::runCachedProject(const QJsonObject &projectInfo)
 {
     const QString projectId = projectInfo.value("id").toString();
diff --git a/src/projectManager.h b/src/projectmanager.h
similarity index 98%
rename from src/projectManager.h
rename to src/projectmanager.h
index 9cc3b95..8c79fac 100644
--- a/src/projectManager.h
+++ b/src/projectmanager.h
@@ -46,6 +46,7 @@ public:
     bool cacheProject(const QByteArray &projectData, const QJsonObject &projectInfo);
     bool isProjectCached(const QJsonObject &projectInfo);
     bool runCachedProject(const QJsonObject &projectInfo);
+    void clearCachedProject(const QJsonObject &projectInfo);
 
     bool cacheDemoProject(const QByteArray &projectData, const QJsonObject &projectInfo);
     bool isDemoProjectCached(const QJsonObject &projectName);
diff --git a/src/qrscanner.cpp b/src/qrscanner.cpp
new file mode 100644
index 0000000..7d26ae2
--- /dev/null
+++ b/src/qrscanner.cpp
@@ -0,0 +1,163 @@
+/****************************************************************************
+**
+** Copyright (C) 2024 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Viewer of the Qt Toolkit.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "qrscanner.h"
+
+#include <QApplication>
+#include <QCamera>
+#include <QCameraDevice>
+#include <QElapsedTimer>
+#include <QImageCapture>
+#include <QMediaDevices>
+#include <QMessageBox>
+#include <QPermission>
+#include <QVideoSink>
+#include <QtConcurrent>
+
+#include "../3rdparty/zxing-cpp/example/ZXingQtReader.h"
+
+using namespace ZXingQt;
+
+QrScanner::~QrScanner()
+{
+    if (m_captureSession) {
+        m_captureSession->camera()->stop();
+        m_captureSession->camera()->deleteLater();
+        m_captureSession->videoOutput()->deleteLater();
+        m_captureSession.clear();
+    }
+}
+
+void QrScanner::scanQrCode()
+{
+    // request permissions
+    QCameraPermission permission;
+    QCoreApplication &app = *QCoreApplication::instance();
+    switch (app.checkPermission(permission)) {
+    case Qt::PermissionStatus::Granted:
+        openCamera();
+        break;
+    case Qt::PermissionStatus::Undetermined:
+    case Qt::PermissionStatus::Denied:
+        app.requestPermission(permission, [this](const QPermission &permission) {
+            if (permission.status() == Qt::PermissionStatus::Denied) {
+                QMessageBox msgBox{QMessageBox::Critical,
+                                   "Critical:",
+                                   "Camera permission denied",
+                                   QMessageBox::Ok};
+                msgBox.exec();
+                return;
+            }
+
+            openCamera();
+        });
+        break;
+    }
+}
+
+void QrScanner::openCamera()
+{
+    // start camera
+    m_captureSession.reset(new QMediaCaptureSession(this));
+    m_captureSession->setCamera(new QCamera(this));
+    m_captureSession->setVideoOutput(new CustomVideoWidget);
+
+    m_captureSession->camera()->setFocusMode(QCamera::FocusModeAuto);
+
+    CustomVideoWidget *videoWidget = qobject_cast<CustomVideoWidget *>(
+        m_captureSession->videoOutput());
+
+    connect(m_captureSession->videoSink(),
+            &QVideoSink::videoFrameChanged,
+            this,
+            [&](const QVideoFrame &frame) {
+                static int i = 0;
+                static QAtomicInt running = 0;
+
+                if (i++ < 20 || running > 0) {
+                    return;
+                }
+
+                i = 0;
+
+                QtConcurrent::run([=] {
+                    // lock the thread so that only one barcode can be read at a time
+                    running++;
+
+                    QElapsedTimer timer;
+                    timer.start();
+                    QList<Result> results = ReadBarcodes(frame.toImage());
+                    qDebug() << "Barcode detection took" << timer.elapsed() << "ms";
+
+                    // release the lock so that another barcode can be read
+                    running--;
+                    return results;
+                }).then([=](const QList<Result> &results) {
+                    if (results.isEmpty() || !m_captureSession)
+                        return;
+
+                    qDebug() << "Stopping camera";
+                    qobject_cast<CustomVideoWidget *>(m_captureSession->videoOutput())->close();
+                    qDebug() << "Camera stopped";
+
+                    Result result = results.first();
+
+                    qDebug() << "Text:   " << result.text();
+                    qDebug() << "Format: " << result.format();
+                    qDebug() << "Content:" << result.contentType();
+
+                    // we have to use invokeMethod because we are in a different thread
+                    // then where the serviceConnector is created
+                    // QMetaObject::invokeMethod(this,
+                    //                           "parseDesignViewerUrl",
+                    //                           Qt::QueuedConnection,
+                    //                           Q_ARG(QUrl, result.text()));
+                    emit qrCodeScanned(result.text());
+                });
+            });
+
+    // stop camera when app is not active.
+    // i.e. when the user switches to another app or the screen is locked
+    QGuiApplication *app(qobject_cast<QGuiApplication *>(QCoreApplication::instance()));
+    connect(app, &QGuiApplication::applicationStateChanged, this, [&](Qt::ApplicationState state) {
+        if (state != Qt::ApplicationState::ApplicationActive && m_captureSession) {
+            qDebug() << "Application is not active. Stopping camera";
+            qobject_cast<CustomVideoWidget *>(m_captureSession->videoOutput())->close();
+        }
+    });
+
+    // stop camera when video widget is closed
+    connect(videoWidget, &CustomVideoWidget::closed, this, [&] {
+        qDebug() << "Video widget closed. Clearing capture session";
+        m_captureSession->camera()->stop();
+        m_captureSession->camera()->deleteLater();
+        m_captureSession->videoOutput()->deleteLater();
+        m_captureSession.clear();
+    });
+
+    videoWidget->setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint);
+    videoWidget->show();
+    m_captureSession->camera()->start();
+}
diff --git a/src/qrscanner.h b/src/qrscanner.h
new file mode 100644
index 0000000..dab9787
--- /dev/null
+++ b/src/qrscanner.h
@@ -0,0 +1,60 @@
+/****************************************************************************
+**
+** Copyright (C) 2024 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Viewer of the Qt Toolkit.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QMediaCaptureSession>
+#include <QVideoWidget>
+
+class CustomVideoWidget : public QVideoWidget
+{
+    Q_OBJECT
+public:
+    explicit CustomVideoWidget(QWidget *parent = nullptr)
+        : QVideoWidget(parent)
+    {}
+    ~CustomVideoWidget() override {}
+
+    void closeEvent(QCloseEvent *) override { emit closed(); }
+
+signals:
+    void closed();
+};
+
+class QrScanner : public QObject
+{
+    Q_OBJECT
+public:
+    ~QrScanner();
+    void scanQrCode();
+
+private:
+    QSharedPointer<QMediaCaptureSession> m_captureSession;
+
+    void openCamera();
+
+signals:
+    void qrCodeScanned(const QString &qrCode);
+};
diff --git a/src/serviceConnector.cpp b/src/serviceconnector.cpp
similarity index 99%
rename from src/serviceConnector.cpp
rename to src/serviceconnector.cpp
index c65b0ab..422709f 100644
--- a/src/serviceConnector.cpp
+++ b/src/serviceconnector.cpp
@@ -23,7 +23,7 @@
 **
 ****************************************************************************/
 
-#include "serviceConnector.h"
+#include "serviceconnector.h"
 
 #include <QNetworkAccessManager>
 #include <QNetworkReply>
diff --git a/src/serviceConnector.h b/src/serviceconnector.h
similarity index 100%
rename from src/serviceConnector.h
rename to src/serviceconnector.h
diff --git a/ui/main.qml b/ui/main.qml
index 0021ae0..4b69f68 100644
--- a/ui/main.qml
+++ b/ui/main.qml
@@ -26,7 +26,7 @@ Rectangle {
 
         Text {
             id: qdvLabel
-            text: qsTr("Qt UI Viewer2")
+            text: qsTr("Qt UI Viewer")
             anchors.top: parent.bottom
             font.pixelSize: 12
             anchors.topMargin: -22
-- 
GitLab