Skip to content
Snippets Groups Projects
Commit 839b726a authored by Burak Hançerli's avatar Burak Hançerli :headphones:
Browse files

QDS-11466 Download user projects directly via QR code

parent ccfe9419
No related branches found
No related tags found
1 merge request!24QDS-11466 Download user projects directly via QR code
Pipeline #65049 passed
<?xml version="1.0"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.qt.qtdesignviewer"
android:installLocation="auto" android:versionCode="19" android:versionName="1.2">
android:installLocation="auto" android:versionCode="20" android:versionName="1.2">
<!-- %%INSERT_PERMISSIONS -->
<!-- %%INSERT_FEATURES -->
<supports-screens android:anyDensity="true" android:largeScreens="true"
......
......@@ -26,8 +26,6 @@
#include "backend.h"
#include "qapplication.h"
#if !defined(Q_OS_WASM)
#include <QDesktopServices>
#include <QFileInfo>
#include <QJsonArray>
......@@ -40,7 +38,7 @@ Backend::Backend(QObject *parent)
: QObject(parent)
{
// This will allow us to open the app with the QR code
QDesktopServices::setUrlHandler("qtdesignviewer", this, "registerUser");
QDesktopServices::setUrlHandler("qtdesignviewer", this, "parseDesignViewerUrl");
}
void Backend::initialize()
......@@ -74,11 +72,6 @@ void Backend::initialize()
updateUserProjectList();
}
connect(&m_serviceConnector,
&ServiceConnector::downloadProgress,
this,
&Backend::downloadProgress);
qDebug("Initialization complete");
}
......@@ -94,15 +87,17 @@ void Backend::initializeProjectManager()
if (m_projectManager)
return;
qDebug() << "Initializing Project Manager";
m_projectManager.reset(new ProjectManager);
connect(m_projectManager.data(), &ProjectManager::closingProject, this, [&] {
emit popupClose();
qDebug() << "Project Manager is closing";
m_projectManager.reset();
});
qDebug() << "Project Manager is initialized";
m_serviceConnector.reset(new ServiceConnector);
connect(m_serviceConnector.data(),
&ServiceConnector::downloadProgress,
this,
&Backend::downloadProgress);
}
bool Backend::connectDesignStudio()
......@@ -204,7 +199,7 @@ void Backend::runDemoProject(const QString &projectName)
updatePopup("Downloading demo project...", false);
const QString url = "https://designviewer.qt.io/qmlprojects/" + projectName + ".qmlrc";
QByteArray project = m_serviceConnector.fetchProject(url);
QByteArray project = m_serviceConnector->fetchProject(url);
qDebug() << "Demo project is not cached. Trying to download from " << url << " ...";
updatePopup("Caching demo project...");
......@@ -239,15 +234,17 @@ void Backend::clearDemoCaches()
emit popupClose();
}
void Backend::runUserProject(const QString &url)
void Backend::runUserProject(const QString &projectName)
{
initializeProjectManager();
updatePopup("Running user project");
emit popupOpen();
qDebug() << "Running user project:" << projectName;
qDebug("Checking if project is cached. Getting list of available projects...");
const QJsonArray projectList = m_serviceConnector.fetchUserProjectList(m_userHash);
const QString projectName = QFileInfo(url).baseName();
const QJsonArray projectList = m_serviceConnector->fetchUserProjectList(m_userHash);
QString projectLastModified;
QString projectId;
QJsonObject projectInfo;
......@@ -263,7 +260,7 @@ void Backend::runUserProject(const QString &url)
if (!projectCached) {
qDebug("Project is not cached. Downloading...");
updatePopup("Project is not cached. Downloading...", false);
QByteArray projectData = m_serviceConnector.fetchProject(url);
QByteArray projectData = m_serviceConnector->fetchUserProject(m_userHash, projectName);
updatePopup("Caching user project...");
if (!m_projectManager->cacheProject(projectData, projectInfo)) {
......@@ -285,6 +282,30 @@ void Backend::runUserProject(const QString &url)
emit popupClose();
}
void Backend::runOnlineProject(const QString &url)
{
initializeProjectManager();
emit popupOpen();
updatePopup("Downloading...", false);
QByteArray projectData = m_serviceConnector->fetchProject(url);
if (projectData.isEmpty()) {
qCritical() << "Could not download project. Please check the logs for more information.";
return;
}
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::registerUser(const QUrl &url)
{
const QString userHash = url.toString().remove("qtdesignviewer://");
......@@ -298,7 +319,8 @@ void Backend::registerUser(const QUrl &url)
void Backend::updateUserProjectList()
{
qDebug() << "Fetching available project list for user: " << m_userHash;
QJsonArray projectList = m_serviceConnector.fetchUserProjectList(m_userHash);
m_serviceConnector.reset(new ServiceConnector);
QJsonArray projectList = m_serviceConnector->fetchUserProjectList(m_userHash);
m_projectList.clear();
qDebug("List of available projects fetched:");
......@@ -310,4 +332,23 @@ void Backend::updateUserProjectList()
emit projectListChanged();
}
#endif // !defined(Q_OS_WASM)
void Backend::parseDesignViewerUrl(const QUrl &url)
{
QString urlData = url.toString().remove("qtdesignviewer://");
// urlData could be either a direct url to the project or a user hash
// If it is a user hash, we register the user and fetch the project list
// If it is a project url, we submit the url to the text field
// sample url: qtdesignviewer://https://<url>/<project_name>.qmlrc
// sample user hash: qtdesignviewer://17e8907b3b84029384hs8djshdu38476
if (urlData.isEmpty())
return;
else if (urlData.startsWith("https//")) {
urlData.replace("https//", "https://");
emit urlUpdated(urlData);
} else if (urlData.startsWith("https://")) {
emit urlUpdated(urlData);
} else {
qDebug() << "Registering user from QR code";
registerUser(url);
}
}
......@@ -57,7 +57,7 @@ private:
// Other members
QString m_userHash;
ServiceConnector m_serviceConnector;
QScopedPointer<ServiceConnector> m_serviceConnector;
QScopedPointer<ProjectManager> m_projectManager;
QScopedPointer<DesignStudioConnector> m_designStudioConnector;
QThread m_dsConnectorThread;
......@@ -78,15 +78,19 @@ signals:
void popupClose();
void userRegistered();
void networkUpdated(QString);
void urlUpdated(QString);
public slots:
void runUserProject(const QString &url);
void runOnlineProject(const QString &url);
void runUserProject(const QString &projectName);
void runDemoProject(const QString &projectName);
void clearDemoCaches();
void registerUser(const QUrl &url);
bool connectDesignStudio();
void disconnectDesignStudio();
void parseDesignViewerUrl(const QUrl &url);
void registerUser(const QUrl &url);
private slots:
void initializeProjectManager();
};
......
......@@ -35,13 +35,13 @@ static QString appLogs;
void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
QString logPrefix, logSuffix, newLog;
QByteArray localMsg = msg.toLocal8Bit();
const char *file = context.file ? context.file : "";
const char *function = context.function ? context.function : "";
QString logPrefix, logSuffix, newLog;
QMessageBox msgBox(QMessageBox::Critical, "Critical:", msg, QMessageBox::Ok);
switch (type)
{
switch (type) {
case QtDebugMsg:
logPrefix = QStringLiteral("Debug: ");
break;
......@@ -53,7 +53,6 @@ void messageHandler(QtMsgType type, const QMessageLogContext &context, const QSt
break;
case QtCriticalMsg:
logPrefix = QStringLiteral("Critical: ");
msgBox.exec();
break;
case QtFatalMsg:
logPrefix = QStringLiteral("Fatal: ");
......@@ -64,16 +63,13 @@ void messageHandler(QtMsgType type, const QMessageLogContext &context, const QSt
newLog += logPrefix + localMsg + logSuffix + "\n";
__android_log_print(ANDROID_LOG_DEBUG, "Qt_UI_Viewer", "%s", qPrintable(newLog));
static int logCounter = 0;
if (logCounter++ == 100) {
// remove the first line
int index = appLogs.indexOf('\n');
if (index > 0)
appLogs.remove(0, index + 1);
logCounter = 99;
// only show critical and fatal messages in the UI
if (type == QtCriticalMsg || type == QtFatalMsg) {
QMessageBox msgBox{QMessageBox::Critical, "Critical:", msg, QMessageBox::Ok};
msgBox.exec();
if (backend)
backend->setLogs(appLogs += newLog);
}
if (backend)
backend->setLogs(appLogs += newLog);
}
int main(int argc, char *argv[])
......
......@@ -62,6 +62,25 @@ ProjectManager::ProjectManager(QObject *parent)
}
}
ProjectManager::~ProjectManager()
{
cleanupResources();
qDebug() << "ProjectManager destroyed.";
}
void ProjectManager::cleanupResources()
{
const uchar *data;
if (m_projectData.size()) {
qDebug() << "Unregistering the previous data from QRC system. Path: " << m_projectPath
<< " Size: " << m_projectData.size() << " bytes.";
data = reinterpret_cast<const uchar *>(m_projectData.data());
if (!QResource::unregisterResource(data, m_projectPath)) {
qCritical("Cannot unregister the previous resource data.");
}
}
}
QString ProjectManager::unpackProject(const QByteArray &project, bool extractZip)
{
QTemporaryDir tempDir("qmlprojector");
......@@ -85,17 +104,10 @@ QString ProjectManager::unpackProject(const QByteArray &project, bool extractZip
if (extractZip)
qDebug("File could not be extracted. Trying to open it as a resource file.");
const uchar *data;
if (m_projectData.size()) {
qDebug() << "Unregistering the previous data from QRC system. Path: " << m_projectPath
<< " Size: " << m_projectData.size() << " bytes.";
data = reinterpret_cast<const uchar *>(m_projectData.data());
if (!QResource::unregisterResource(data, m_projectPath)) {
qCritical("Cannot unregister the previous resource data.");
}
}
cleanupResources();
m_projectData = project;
const uchar *data;
data = reinterpret_cast<const uchar *>(m_projectData.data());
qDebug() << "Registering resource data. Size: " << m_projectData.size();
......
......@@ -38,6 +38,7 @@ class ProjectManager : public QObject
Q_OBJECT
public:
explicit ProjectManager(QObject *parent = nullptr);
~ProjectManager();
QString unpackProject(const QByteArray &project, bool extractZip = false);
bool runProject(const QString &projectPath);
......@@ -78,6 +79,7 @@ private:
void parseQmlProjectFile(const QString &fileName, QString *mainFile, QStringList *importPaths);
void cacheProject(const QString &projectName, const QString &projectPath);
void cleanupResources();
signals:
void closingProject();
......
......@@ -31,7 +31,7 @@
#include <QJsonDocument>
#include <QJsonObject>
QSharedPointer<QNetworkReply> ServiceConnector::fetchResource(const QString &url)
QByteArray ServiceConnector::fetchResource(const QString &url)
{
qDebug() << "Fetching resource from" << url;
......@@ -64,7 +64,7 @@ QSharedPointer<QNetworkReply> ServiceConnector::fetchResource(const QString &url
else
qDebug() << "Resource fetched successfully";
return reply;
return reply->readAll();
}
QByteArray ServiceConnector::fetchProject(const QString &url)
......@@ -76,26 +76,25 @@ QByteArray ServiceConnector::fetchProject(const QString &url)
projectUrl.prepend("https://designviewer.qt.io/qmlprojects/");
}
auto reply = fetchResource(projectUrl);
if (reply->error() != QNetworkReply::NoError)
{
qCritical("Could not fetch project");
return QByteArray();
}
return fetchResource(projectUrl);
}
return reply->readAll();
QByteArray ServiceConnector::fetchUserProject(const QString &userHash, const QString &projectName)
{
const QString projectUrl = "https://designviewer.qt.io/qmlprojects/" + userHash + "/"
+ projectName + ".qmlrc";
return fetchResource(projectUrl);
}
QJsonArray ServiceConnector::fetchUserProjectList(const QString &userHash)
{
auto reply = fetchResource("https://designviewer.qt.io/api/v1/qmlrc/list/" + userHash + "?key=818815");
if (reply->error() != QNetworkReply::NoError)
{
if (reply.size() == 0) {
qCritical("Could not fetch available project list");
return QJsonArray();
}
QJsonArray projectList = QJsonDocument::fromJson(reply->readAll())
QJsonArray projectList = QJsonDocument::fromJson(reply)
.object()
.value("data")
.toObject()
......
......@@ -34,11 +34,12 @@ class ServiceConnector : public QObject
Q_OBJECT
public:
QByteArray fetchProject(const QString &url);
QByteArray fetchUserProject(const QString &userHash, const QString &projectName);
QJsonArray fetchUserProjectList(const QString &userHash);
private:
QNetworkAccessManager m_manager;
QSharedPointer<QNetworkReply> fetchResource(const QString &url);
QByteArray fetchResource(const QString &url);
signals:
void downloadProgress(float percentage);
......
......@@ -47,32 +47,64 @@ Item {
Layout.fillWidth: true
}
ComboBox {
id: projectList
ColumnLayout {
id: column2
Layout.fillWidth: true
onCurrentIndexChanged: {
urlTextField.text = "https://designviewer.qt.io/qmlprojects/"
+ backend.userHash + "/"
+ textAt(currentIndex)
+ ".qmlrc"
displayText = textAt(currentIndex)
}
onModelChanged: {
if (model.count > 0) {
currentIndex = 0
ComboBox {
id: projectList
Layout.fillWidth: true
onCurrentIndexChanged: {
displayText = textAt(currentIndex)
downloadUserProject.enabled = true
}
onModelChanged: {
if (model.count > 0) {
currentIndex = 0
}
}
model: backend.projectList
down: false
displayText: "Select project..."
currentIndex: -1
Layout.preferredHeight: 50
}
Button {
id: downloadUserProject
text: qsTr("Run Project")
onClicked: backend.runUserProject(projectList.currentText)
enabled: backend.userHash !== ''
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
}
model: backend.projectList
down: false
displayText: "Select project..."
currentIndex: -1
Layout.preferredHeight: 50
}
Item {
id: item3
Layout.fillHeight: true
Layout.fillWidth: true
Layout.preferredHeight: 10
Layout.preferredWidth: 10
}
ColumnLayout {
id: column
Layout.fillWidth: true
Text {
id: downloadInstructions
text: qsTr("Enter project URL and then click the button below")
font.pixelSize: 12
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
TextField {
id: urlTextField
horizontalAlignment: Text.AlignHCenter
......@@ -85,26 +117,20 @@ Item {
id: rowLayout
Button {
id: downloadButton
text: qsTr("Download and Run")
onClicked: backend.runUserProject(urlTextField.text)
text: qsTr("Download and Run Project")
onClicked: backend.runOnlineProject(urlTextField.text)
enabled: urlTextField.text !== ''
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
}
}
}
Item {
id: item1
Layout.fillHeight: true
Layout.fillWidth: true
Layout.preferredHeight: 10
Layout.preferredWidth: 10
Connections {
target: backend
function onUrlUpdated(newUrl){
urlTextField.text = newUrl;
}
}
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment