From 9a344f35821ca0566e0b6fa35fe70ab0d2934349 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Burak=20Han=C3=A7erli?= <burak.hancerli@qt.io>
Date: Wed, 15 Nov 2023 11:11:11 +0000
Subject: [PATCH] QDS-11236 Simple caching method for the demos

---
 android/AndroidManifest.xml |  2 +-
 src/backend.cpp             | 56 ++++++++++++++++-------
 src/backend.h               |  3 +-
 src/main.cpp                | 13 +++---
 src/projectManager.cpp      | 89 ++++++++++++++++++++++++++++++++++---
 src/projectManager.h        | 17 +++++--
 ui/ExamplesPage.qml         | 13 ++++--
 7 files changed, 156 insertions(+), 37 deletions(-)

diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml
index 151df22..b3d4af6 100644
--- a/android/AndroidManifest.xml
+++ b/android/AndroidManifest.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.qt.qtdesignviewer"
-    android:installLocation="auto" android:versionCode="13" android:versionName="1.2">
+    android:installLocation="auto" android:versionCode="14" android:versionName="1.2">
     <!-- %%INSERT_PERMISSIONS -->
     <!-- %%INSERT_FEATURES -->
     <supports-screens android:anyDensity="true" android:largeScreens="true"
diff --git a/src/backend.cpp b/src/backend.cpp
index 0fd27c4..15fe089 100644
--- a/src/backend.cpp
+++ b/src/backend.cpp
@@ -86,34 +86,56 @@ void Backend::showWarning(const QString &message)
 {
     QMessageBox msg(QMessageBox::Warning, "Warning", message, QMessageBox::Ok);
     msg.exec();
+    qWarning() << message;
 }
 
-void Backend::downloadAndRun(const QString &url)
+void Backend::updatePopup(const QString &text, bool indeterminate)
 {
-    updatePopup("Downloading project...", false);
+    emit popupTextChanged(text);
+    emit popupProgressIndeterminateChanged(indeterminate);
+    QEventLoop().processEvents(QEventLoop::AllEvents, 100);
+}
+
+void Backend::runDemoProject(const QString &projectName)
+{
+    qDebug() << "Checking if demo project is cached for " << projectName;
     emit popupOpen();
 
-    qDebug() << "Fetching project from " << url << "...";
-    QByteArray project = m_serviceConnector.fetchProject(url);
+    const bool cached = m_projectManager.isDemoProjectCached(projectName);
 
-    updatePopup("Unpacking project...");
+    if (!cached) {
+        updatePopup("Downloading demo project...", false);
 
-    const QString projectPath = m_projectManager.unpackProject(project);
+        const QString url = "https://designviewer.qt.io/qmlprojects/" + projectName + ".qmlrc";
+        QByteArray project = m_serviceConnector.fetchProject(url);
+        qDebug() << "Demo project is not cached. Trying to download from " << url << " ...";
+
+        updatePopup("Caching demo project...");
+        if (!m_projectManager.cacheDemoProject(project, projectName)) {
+            showWarning(
+                "Could not cache demo project. Please check the logs for more information.");
+            emit popupClose();
+            return;
+        }
+    } else {
+        qDebug() << "Demo project is cached. Running cached project...";
+    }
 
-    updatePopup("Running project...");
-    if (!m_projectManager.runProject(projectPath, QFileInfo(url).baseName()))
-        qCritical("Could not run project");
+    updatePopup("Running demo project...");
+    if (!m_projectManager.runDemoProject(projectName))
+        showWarning("Could not run demo project. Please check the logs for more information.");
     else
         m_projectManager.showAppWindow();
 
     emit popupClose();
 }
 
-void Backend::updatePopup(const QString &text, bool indeterminate)
+void Backend::clearDemoCaches()
 {
-    emit popupTextChanged(text);
-    emit popupProgressIndeterminateChanged(indeterminate);
-    QEventLoop().processEvents(QEventLoop::AllEvents, 100);
+    emit popupOpen();
+    updatePopup("Clearing demo caches...");
+    m_projectManager.clearDemoCaches();
+    emit popupClose();
 }
 
 void Backend::runUserProject(const QString &url)
@@ -142,14 +164,18 @@ void Backend::runUserProject(const QString &url)
         QByteArray projectData = m_serviceConnector.fetchProject(url);
 
         updatePopup("Caching user project...");
-        m_projectManager.cacheProject(projectData, projectInfo);
+        if (!m_projectManager.cacheProject(projectData, projectInfo)) {
+            showWarning("Could not cache project. Please check the logs for more information.");
+            emit popupClose();
+            return;
+        }
     } else {
         qDebug("Project is cached. Running cached project...");
     }
 
     updatePopup("Running cached project...");
     if (!m_projectManager.runCachedProject(projectInfo))
-        qCritical("Could not run project");
+        showWarning("Could not run project. Please check the logs for more information.");
     else
         m_projectManager.showAppWindow();
 
diff --git a/src/backend.h b/src/backend.h
index af0e181..c17d6d6 100644
--- a/src/backend.h
+++ b/src/backend.h
@@ -77,7 +77,8 @@ signals:
 
 public slots:
     void runUserProject(const QString &url);
-    void downloadAndRun(const QString &url);
+    void runDemoProject(const QString &projectName);
+    void clearDemoCaches();
     void registerUser(const QUrl &url);
 };
 
diff --git a/src/main.cpp b/src/main.cpp
index 823dc0a..d10669e 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -29,14 +29,15 @@
 
 #include "backend.h"
 
-Backend *backend;
+static Backend *backend;
+static QString appLogs;
 
 void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
 {
     QByteArray localMsg = msg.toLocal8Bit();
     const char *file = context.file ? context.file : "";
     const char *function = context.function ? context.function : "";
-    QString logPrefix, logSuffix, applicationLogs;
+    QString logPrefix, logSuffix, newLog;
 
     switch (type)
     {
@@ -57,11 +58,11 @@ void messageHandler(QtMsgType type, const QMessageLogContext &context, const QSt
         logSuffix = QStringLiteral(" (%1:%2, %3)").arg(file).arg(context.line).arg(function);
         break;
     }
-    applicationLogs += logPrefix + localMsg + logSuffix + "\n";
-    __android_log_print(ANDROID_LOG_DEBUG, "Qt_Design_Viewer", "%s", qPrintable(applicationLogs));
+    newLog += logPrefix + localMsg + logSuffix + "\n";
+    __android_log_print(ANDROID_LOG_DEBUG, "Qt_Design_Viewer", "%s", qPrintable(newLog));
 
     if (backend)
-        backend->setLogs(applicationLogs);
+        backend->setLogs(appLogs += newLog);
 }
 
 int main(int argc, char *argv[])
@@ -81,7 +82,5 @@ int main(int argc, char *argv[])
     view.showMaximized();
 
     backend->initialize();
-    backend->registerUser(QUrl("17e8907b3b84b8206d45be4f551f4e25"));
-
     return app.exec();
 }
diff --git a/src/projectManager.cpp b/src/projectManager.cpp
index 37f3654..fc0249d 100644
--- a/src/projectManager.cpp
+++ b/src/projectManager.cpp
@@ -46,9 +46,22 @@
 
 ProjectManager::ProjectManager(QObject *parent)
     : QObject(parent)
+    , m_projectCachePath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))
+    , m_demoProjectCachePath(m_projectCachePath + "/demoProjects")
 {
     qDebug() << "ProjectManager created.";
-    m_projectCachePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
+    qDebug() << "Project cache path: " << m_projectCachePath;
+    qDebug() << "Demo project cache path: " << m_demoProjectCachePath;
+
+    if (!QDir(m_projectCachePath).exists()) {
+        qDebug() << "Creating project cache path: " << m_projectCachePath;
+        QDir().mkpath(m_projectCachePath);
+    }
+
+    if (!QDir(m_demoProjectCachePath).exists()) {
+        qDebug() << "Creating demo project cache path: " << m_demoProjectCachePath;
+        QDir().mkpath(m_demoProjectCachePath);
+    }
 }
 
 QString ProjectManager::unpackProject(const QByteArray &project, bool extractZip)
@@ -215,13 +228,17 @@ bool ProjectManager::runProject(const QString &projectPath, const QString &proje
         qputenv("QT_QUICK_CONTROLS_CONF", qtquickcontrols2File.toLatin1());
     }
 
+    qDebug() << "Initializing the qmlEngine";
+    m_qmlEngine.reset(new QQmlEngine);
+    m_qmlEngine->clearComponentCache();
+
     qDebug("Adding import paths");
     for (const QString &importPath : importPaths) {
         qDebug() << "-- Import path: " << importPath;
-        m_qmlEngine.addImportPath(importPath);
+        m_qmlEngine->addImportPath(importPath);
     }
 
-    QObject::connect(&m_qmlEngine,
+    QObject::connect(m_qmlEngine.data(),
                      &QQmlEngine::warnings,
                      this,
                      [&](const QList<QQmlError> &warnings) {
@@ -234,7 +251,7 @@ bool ProjectManager::runProject(const QString &projectPath, const QString &proje
     QEventLoop().processEvents(QEventLoop::AllEvents, 1000);
 
     qDebug() << "Loading mainQmlUrl: " << mainQmlUrl.toString();
-    m_qmlComponent.reset(new QQmlComponent(&m_qmlEngine));
+    m_qmlComponent.reset(new QQmlComponent(m_qmlEngine.data()));
     m_qmlComponent->loadUrl(mainQmlUrl);
 
     qDebug() << "Waiting for qmlComponent to load";
@@ -260,7 +277,7 @@ bool ProjectManager::runProject(const QString &projectPath, const QString &proje
     m_quickWindow.reset(qobject_cast<QQuickWindow *>(topLevel));
     if (m_quickWindow) {
         qDebug() << "Running with incubator controller";
-        m_qmlEngine.setIncubationController(m_quickWindow->incubationController());
+        m_qmlEngine->setIncubationController(m_quickWindow->incubationController());
     } else {
         qWarning() << "Top level object is not a QQuickWindow. Trying QQuickView...";
 
@@ -271,7 +288,7 @@ bool ProjectManager::runProject(const QString &projectPath, const QString &proje
         }
 
         qDebug() << "Initializing QQuickView";
-        QQuickView *view = new QQuickView(&m_qmlEngine, nullptr);
+        QQuickView *view = new QQuickView(m_qmlEngine.data(), nullptr);
         m_quickWindow.reset(view);
         view->setContent(mainQmlUrl, m_qmlComponent.data(), contentItem);
         view->setResizeMode(QQuickView::SizeViewToRootObject);
@@ -386,6 +403,66 @@ bool ProjectManager::runCachedProject(const QJsonObject &projectInfo)
     return runProject(projectPath, projectName);
 }
 
+bool ProjectManager::cacheDemoProject(const QByteArray &projectData, const QString &projectName)
+{
+    const QString demoProjectPath = m_demoProjectCachePath + "/" + projectName;
+    qDebug() << "Caching demo project " << projectName << " to " << demoProjectPath;
+
+    // remove old cache
+    if (QDir(demoProjectPath).exists()) {
+        qDebug() << "Removing old cache for demo project " << projectName;
+        if (!QDir(demoProjectPath).removeRecursively()) {
+            qCritical() << "Could not remove old cache for demo project " << projectName;
+            return false;
+        }
+    }
+
+    // create new cache
+    if (!QDir().mkpath(demoProjectPath)) {
+        qCritical() << "Could not create new cache for demo project " << projectName;
+        return false;
+    }
+
+    const QString tempProjectPath = unpackProject(projectData);
+
+    // copy all files from tempProjectPath to demoProjectPath
+    QDirIterator it(tempProjectPath, QDirIterator::Subdirectories);
+    while (it.hasNext()) {
+        it.next();
+        const QString filePath = it.filePath();
+        const QString relativeFilePath = filePath.mid(tempProjectPath.length());
+        const QString newFilePath = demoProjectPath + relativeFilePath;
+        qDebug() << "Copying " << filePath << " to " << newFilePath;
+        if (QFileInfo(filePath).isDir()) {
+            QDir().mkpath(newFilePath);
+        } else if (!QFile::copy(filePath, newFilePath)) {
+            qCritical() << "Could not copy " << filePath << " to " << newFilePath;
+            return false;
+        }
+    }
+    return true;
+}
+
+void ProjectManager::clearDemoCaches()
+{
+    qDebug() << "Clearing demo caches";
+    QDir(m_demoProjectCachePath).removeRecursively();
+}
+
+bool ProjectManager::isDemoProjectCached(const QString &projectName)
+{
+    const QString demoProjectPath = m_demoProjectCachePath + "/" + projectName;
+    qDebug() << "Checking if demo project " << projectName << " is cached";
+    return QDir(demoProjectPath).exists();
+}
+
+bool ProjectManager::runDemoProject(const QString &projectName)
+{
+    const QString demoProjectPath = m_demoProjectCachePath + "/" + projectName;
+    qDebug() << "Running demo project " << projectName << " from " << demoProjectPath;
+    return runProject(demoProjectPath, projectName);
+}
+
 void ProjectManager::orientateWindow(Qt::ScreenOrientation orientation)
 {
     QQuickItem *contentItem = m_quickWindow->contentItem();
diff --git a/src/projectManager.h b/src/projectManager.h
index e036e72..3fc0759 100644
--- a/src/projectManager.h
+++ b/src/projectManager.h
@@ -38,11 +38,19 @@ class ProjectManager : public QObject
     Q_OBJECT
 public:
     explicit ProjectManager(QObject *parent = nullptr);
+
     QString unpackProject(const QByteArray &project, bool extractZip = false);
     bool runProject(const QString &projectPath, const QString &projectName);
+
     bool cacheProject(const QByteArray &projectData, const QJsonObject &projectInfo);
     bool isProjectCached(const QJsonObject &projectInfo);
     bool runCachedProject(const QJsonObject &projectInfo);
+
+    bool cacheDemoProject(const QByteArray &projectData, const QString &projectName);
+    bool isDemoProjectCached(const QString &projectName);
+    bool runDemoProject(const QString &projectName);
+    void clearDemoCaches();
+
     void showAppWindow();
     void hideAppWindow();
 
@@ -53,12 +61,13 @@ private:
     // Member variables
     QByteArray m_projectData;
     QString m_projectPath;
-    QString m_projectCachePath;
+    const QString m_projectCachePath;
+    const QString m_demoProjectCachePath;
 
     // Qml related members
-    QQmlEngine m_qmlEngine;
-    QSharedPointer<QQmlComponent> m_qmlComponent;
-    QSharedPointer<QQuickWindow> m_quickWindow;
+    QScopedPointer<QQmlEngine> m_qmlEngine;
+    QScopedPointer<QQmlComponent> m_qmlComponent;
+    QScopedPointer<QQuickWindow> m_quickWindow;
 
     // Member functions
     QString findFile(const QString &dir, const QString &filter);
diff --git a/ui/ExamplesPage.qml b/ui/ExamplesPage.qml
index c673676..fb8b1f6 100644
--- a/ui/ExamplesPage.qml
+++ b/ui/ExamplesPage.qml
@@ -13,7 +13,7 @@ Item {
             id: button3
             text: qsTr("Cluster Tutorial")
             Layout.fillWidth: true
-            onClicked: backend.downloadAndRun("https://designviewer.qt.io/#ClusterTutorial.qmlrc")
+            onClicked: backend.runDemoProject("ClusterTutorial")
         }
 
 
@@ -21,14 +21,14 @@ Item {
             id: button
             text: qsTr("E-Bike Design")
             Layout.fillWidth: true
-            onClicked: backend.downloadAndRun("https://designviewer.qt.io/#EBikeDesign.qmlrc")
+            onClicked: backend.runDemoProject("EBikeDesign")
         }
 
         Button {
             id: button4
             text: qsTr("Material Bundle")
             Layout.fillWidth: true
-            onClicked: backend.downloadAndRun("https://designviewer.qt.io/#MaterialBundle.qmlrc")
+            onClicked: backend.runDemoProject("MaterialBundle")
         }
 
         Item {
@@ -38,5 +38,12 @@ Item {
             Layout.fillHeight: true
             Layout.fillWidth: true
         }
+
+        Button {
+            id: button5
+            text: qsTr("Clear demo caches")
+            Layout.fillWidth: true
+            onClicked: backend.clearDemoCaches()
+        }
     }
 }
-- 
GitLab