-
Burak Hançerli authoredBurak Hançerli authored
backend.cpp 16.56 KiB
/****************************************************************************
**
** Copyright (C) 2023 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 "backend.h"
#include "backend/constants.h"
#include <QDesktopServices>
#include <QEventLoop>
#include <QFileInfo>
#include <QGuiApplication>
#include <QJniObject>
#include <QJsonArray>
#include <QJsonObject>
#include <QSettings>
#include <QSysInfo>
#include <QTimer>
// Q_DECLARE_JNI_CLASS(Secure, "android/provider/Settings$Secure");
// Q_DECLARE_JNI_CLASS(String, "java/lang/String");
// Q_DECLARE_JNI_CLASS(ContentResolver, "android/content/ContentResolver");
Backend::Backend(QObject *parent)
: QObject(parent)
{
// This will allow us to open the app with the QR code
QDesktopServices::setUrlHandler("qtdesignviewer", this, "parseDesignViewerUrl");
QDesktopServices::setUrlHandler("qtdesignstudio", this, "parseDesignViewerUrl");
QDesktopServices::setUrlHandler("https", this, "parseDesignViewerUrl");
// Initialize background update
connect(&m_projectListUpdateTimer, &QTimer::timeout, this, &Backend::updateUserProjectList);
m_projectListUpdateTimer.setInterval(1000 * 10);
m_projectListUpdateTimer.start();
updateUserProjectList();
initDesignStudioManager();
connect(qApp,
&QGuiApplication::applicationStateChanged,
this,
[this](Qt::ApplicationState state) {
qDebug() << "Application state changed to:" << state;
if (state == Qt::ApplicationState::ApplicationActive) {
m_dsConnectorThread.start();
m_projectListUpdateTimer.start();
} else if (state == Qt::ApplicationState::ApplicationSuspended) {
m_dsConnectorThread.quit();
m_projectListUpdateTimer.stop();
}
});
const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry();
// Get the unique device ID
auto context = QNativeInterface::QAndroidApplication::context();
// auto contentResolver = context.callMethod<QtJniTypes::ContentResolver>("getContentResolver");
// auto androidId = QtJniTypes::Secure::callStaticMethod<jstring>("getString",
// contentResolver,
// QStringLiteral("android_id"));
// const QByteArray serial = androidId.toString().toLatin1();
qDebug() << "Qt Design Viewer";
qDebug() << "System information:";
qDebug() << "-- Qt version: " << QT_VERSION_STR;
qDebug() << "-- OpenSSL support: " << QVariant(QSslSocket::supportsSsl()).toString();
qDebug() << "-- Screen height: " << QString::number(screenGeometry.height());
qDebug() << "-- Screen width: " << QString::number(screenGeometry.width());
qDebug() << "-- OS: " << QSysInfo::prettyProductName();
qDebug() << "-- OS version: " << QSysInfo::productVersion();
qDebug() << "-- Architecture: " << QSysInfo::currentCpuArchitecture();
qDebug() << "-- Boot ID: " << QSysInfo::bootUniqueId();
qDebug() << "-- Kernel type: " << QSysInfo::kernelType();
qDebug() << "-- Kernel version: " << QSysInfo::kernelVersion();
qDebug() << "-- Machine unique ID: " << QSysInfo::machineUniqueId();
qDebug() << "-- Product type: " << QSysInfo::productType();
qDebug() << "-- Product version: " << QSysInfo::productVersion();
qDebug() << "-- Build ABI: " << QSysInfo::buildAbi();
qDebug() << "-- Build CPU architecture: " << QSysInfo::buildCpuArchitecture();
// qDebug() << "-- Unique device ID: " << serial;
qDebug() << "Thread id backend:" << QThread::currentThreadId();
}
QString Backend::buildInfo() const
{
#ifdef QT_DEBUG
const QString buildType = "Debug";
#else
const QString buildType = "Release";
#endif
return {QCoreApplication::applicationVersion() + "\nTechnology Preview - "
+ QString(CMAKE_VAR_GIT_VERSION) + "\nQt " + QString(QT_VERSION_STR) + " - " + buildType
+ " Build" + "\nQt Quick Components " + QString(CMAKE_VAR_QT_QUICK_COMPONENTS_VERSION)
+ "\nZXing-Cpp: " + QString(CMAKE_VAR_ZXING_VERSION)
+ "\nOpenSSL support: " + QVariant(QSslSocket::supportsSsl()).toString()};
}
void Backend::setAutoScaleProject(const bool &enabled)
{
QSettings().setValue(Constants::Settings::ProjectManager::AutoScale, enabled);
if (enabled) {
qDebug() << "Auto scale project is enabled";
} else {
qDebug() << "Auto scale project is disabled";
}
}
void Backend::setUserHash(const QString &userHash)
{
QSettings().setValue(Constants::Settings::Backend::UserHash, userHash);
emit userHashChanged();
}
bool Backend::autoScaleProject()
{
return QSettings().value(Constants::Settings::ProjectManager::AutoScale, true).toBool();
}
QString Backend::userHash()
{
return QSettings().value(Constants::Settings::Backend::UserHash).toString();
}
void Backend::updatePopup(const QString &text, bool indeterminate)
{
emit popupTextChanged(text);
emit popupProgressIndeterminateChanged(indeterminate);
QEventLoop().processEvents(QEventLoop::AllEvents, 1000);
}
void Backend::initializeProjectManager()
{
m_projectManager.reset(new ProjectManager);
connect(m_projectManager.get(), &ProjectManager::closingProject, this, [&] {
emit popupClose();
m_projectManager.reset();
});
}
void Backend::initDesignStudioManager()
{
connect(&m_dsConnectorThread, &QThread::started, [this] {
qDebug() << "Design Studio Manager thread started";
m_designStudioManager.reset(new DesignStudioManager);
// signals goes from DS Manager to UI
connect(m_designStudioManager.get(),
&DesignStudioManager::registrationPinRequested,
this,
&Backend::pinRequested);
connect(m_designStudioManager.get(),
&DesignStudioManager::projectReceived,
this,
&Backend::runDsProject);
// signals goes from UI to DS Manager
connect(this,
&Backend::enterPin,
m_designStudioManager.get(),
&DesignStudioManager::enterPin);
});
connect(&m_dsConnectorThread, &QThread::finished, this, [this] {
qDebug() << "Design Studio Manager thread finished";
m_designStudioManager.reset();
});
connect(&m_serviceConnector,
&ServiceConnector::downloadProgress,
this,
&Backend::downloadProgress);
}
void Backend::runDsProject(const QByteArray &projectData)
{
initializeProjectManager();
emit popupOpen();
updatePopup("Unpacking project...");
QString projectPath = m_projectManager->unpackProject(projectData);
updatePopup("Running project...");
if (!m_projectManager->runProject(projectPath))
qCritical() << "Could not run project. Please check the logs for more information.";
else {
m_projectManager->showAppWindow();
}
emit popupClose();
}
void Backend::runDemoProject(const QString &projectName)
{
initializeProjectManager();
qDebug() << "Checking if demo project is cached for " << projectName;
emit popupOpen();
// sample project info
// [{"lastUpdate":1701947766739.9812,"name":"ClusterTutorial.qmlrc"}]
const std::optional<QJsonArray> projectList = m_serviceConnector.fetchDemoList();
if (projectList == std::nullopt) {
qCritical()
<< "Could not fetch demo project list. Please check your internet connection and "
"try again.";
emit popupClose();
return;
}
QJsonObject projectInfo;
for (auto project : projectList.value()) {
if (projectName == project.toObject().value("name").toString().remove(".qmlrc")) {
projectInfo = project.toObject();
break;
}
}
const bool cached = m_projectManager->isDemoProjectCached(projectInfo);
if (!cached) {
updatePopup("Downloading demo project...", false);
const std::optional<QByteArray> project = m_serviceConnector.fetchDemo(projectName);
if (project == std::nullopt) {
qCritical() << "Could not download demo project. Please check the logs for more "
"information.";
emit popupClose();
return;
}
updatePopup("Caching demo project...");
if (!m_projectManager->cacheDemoProject(project.value(), projectInfo)) {
qCritical()
<< "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 demo project...");
if (!m_projectManager->runDemoProject(projectName))
qCritical() << "Could not run demo project. Please check the logs for more information.";
else {
updatePopup("Showing the app window...");
m_projectManager->showAppWindow();
}
emit popupClose();
}
void Backend::clearDemoCaches()
{
emit popupOpen();
updatePopup("Clearing demo caches...");
ProjectManager().clearDemoCaches();
emit popupClose();
}
void Backend::runUserProject(const QString &projectName, const QString &password)
{
initializeProjectManager();
updatePopup("Running user project");
emit popupOpen();
qDebug() << "Running user project:" << projectName;
// fetch the project list to check if the project is cached
const std::optional<QJsonArray> projectList = m_serviceConnector.fetchUserProjectList(
userHash());
if (projectList == std::nullopt) {
qCritical()
<< "Could not fetch user project list. Please check your internet connection and "
"try again.";
emit popupClose();
return;
}
QJsonObject projectInfo;
for (const auto &project : projectList.value()) {
if (projectName == project.toObject().value("appName").toString()) {
projectInfo = project.toObject();
break;
}
}
const bool projectCached = m_projectManager->isProjectCached(projectInfo);
if (!projectCached) {
qDebug("Project is not cached. Downloading...");
updatePopup("Project is not cached. Downloading...", false);
const std::optional<QByteArray> projectData
= m_serviceConnector.fetchUserProject(userHash(), projectName, password);
if (projectData == std::nullopt) {
qCritical()
<< "Could not download project. Please check the logs for more information.";
emit popupClose();
return;
}
updatePopup("Caching user project...");
if (!m_projectManager->cacheProject(projectData.value(), projectInfo)) {
qCritical() << "Could not cache project. Please check the logs for more information.";
emit popupClose();
return;
}
}
qDebug("Project is cached. Running cached project...");
updatePopup("Running cached project...");
if (!m_projectManager->runCachedProject(projectInfo))
qCritical() << "Could not run project. Please check the logs for more information.";
else {
updatePopup("Showing the app window...");
m_projectManager->showAppWindow();
}
emit popupClose();
}
void Backend::runOnlineProject(const QString &url)
{
initializeProjectManager();
emit popupOpen();
updatePopup("Downloading...", false);
const std::optional<QByteArray> projectData = m_serviceConnector.fetchProject(url);
if (projectData == std::nullopt) {
qCritical() << "Could not download project. Please check the logs for more information.";
emit popupClose();
return;
}
updatePopup("Unpacking project...");
QString projectPath = m_projectManager->unpackProject(projectData.value());
updatePopup("Running project...");
if (!m_projectManager->runProject(projectPath))
qCritical() << "Could not run project. Please check the logs for more information.";
else {
m_projectManager->showAppWindow();
}
emit popupClose();
}
void Backend::updateUserProjectList()
{
const QString userHash = Backend::userHash();
if (userHash.isEmpty()) {
return;
}
qDebug() << "Fetching available project list for user:" << userHash;
const std::optional<QJsonArray> projectList = m_serviceConnector.fetchUserProjectList(userHash);
if (projectList == std::nullopt) {
qWarning(
"Could not fetch project list. Please check your internet connection and try again.");
} else if (projectList.value() == m_projectList) {
qDebug("No new projects are available");
} else {
qDebug("List of available projects fetched:");
for (const auto &project : projectList.value()) {
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
// because this triggers the onModelChanged function in the QML
// in order to update the UI
m_projectList = projectList.value();
emit projectListChanged();
}
void Backend::scanQrCode()
{
m_qrScanner.reset(new QrScanner);
connect(m_qrScanner.get(), &QrScanner::qrCodeScanned, this, [&](const QString &qrCode) {
qDebug() << "QR code scanned:" << qrCode;
parseDesignViewerUrl(QUrl(qrCode));
m_qrScanner.reset();
});
m_qrScanner->scanQrCode();
}
void Backend::parseDesignViewerUrl(const QUrl &url)
{
// url format could be one of the following:
// - https://<url>/<project_name>.qmlrc
// - qtdesignviewer://<user_hash>
// - qtdesignstudio://<ipv4_address>?<design_studio_id>
if (url.scheme() == "https") {
emit urlUpdated(url.toString());
} else if (url.scheme() == "qtdesignviewer") {
qDebug() << "Registering user from QR code";
setUserHash(url.host());
updateUserProjectList();
} else if (url.scheme() == "qtdesignstudio") {
qDebug() << "Connecting to Design Studio from QR code";
if (url.host().isEmpty()) {
qWarning() << "No Design Studio IP address found in the QR code";
return;
} else if (url.query().isEmpty()) {
qWarning() << "No Design Studio ID found in the QR code";
return;
}
const QString ipv4Addr = url.host();
const QString designStudioId = url.query();
QMetaObject::invokeMethod(m_designStudioManager.get(),
"designStudioFound",
Qt::QueuedConnection,
Q_ARG(QString, ipv4Addr),
Q_ARG(QString, designStudioId));
} else {
qWarning() << "Unknown QR code format";
qWarning() << "URL:" << url.toString();
}
}
void Backend::popupInterrupted()
{
qDebug() << "Popup closed prematurely. Interrupting active downloads (if any)";
QMetaObject::invokeMethod(&m_serviceConnector, "interrupted", Qt::QueuedConnection);
}