QmlDesigner: Add support for component unimporting from bundle

Fixes: QDS-7706
Change-Id: Ib0ab71b650592b1796d3ef2f14e364431a82a268
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
This commit is contained in:
Miikka Heikkinen
2022-09-16 16:29:25 +03:00
parent d9f07e95b5
commit 145ecd040f
3 changed files with 145 additions and 32 deletions

View File

@@ -33,9 +33,9 @@
#include <qmljs/qmljsmodelmanagerinterface.h> #include <qmljs/qmljsmodelmanagerinterface.h>
#include <QFile>
#include <QFileInfo> #include <QFileInfo>
#include <QSaveFile> #include <QJsonDocument>
#include <QJsonObject>
#include <QStringList> #include <QStringList>
using namespace Utils; using namespace Utils;
@@ -65,20 +65,14 @@ BundleImporter::BundleImporter(const QString &bundleDir,
QString BundleImporter::importComponent(const QString &qmlFile, QString BundleImporter::importComponent(const QString &qmlFile,
const QStringList &files) const QStringList &files)
{ {
FilePath bundleImportPath = QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath(); FilePath bundleImportPath = resolveBundleImportPath();
if (bundleImportPath.isEmpty()) if (bundleImportPath.isEmpty())
return "Failed to resolve current project path"; return "Failed to resolve bundle import folder";
const QString projectBundlePath = QStringLiteral("%1%2/%3").arg( bool bundleImportPathExists = bundleImportPath.exists();
QLatin1String(Constants::DEFAULT_ASSET_IMPORT_FOLDER),
QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER),
m_bundleId).mid(1); // Chop leading slash
bundleImportPath = bundleImportPath.resolvePath(projectBundlePath);
if (!bundleImportPath.exists()) { if (!bundleImportPathExists && !bundleImportPath.createDir())
if (!bundleImportPath.createDir()) return QStringLiteral("Failed to create bundle import folder: '%1'").arg(bundleImportPath.toString());
return QStringLiteral("Failed to create bundle import folder: '%1'").arg(bundleImportPath.toString());
}
for (const QString &file : qAsConst(m_sharedFiles)) { for (const QString &file : qAsConst(m_sharedFiles)) {
FilePath target = bundleImportPath.resolvePath(file); FilePath target = bundleImportPath.resolvePath(file);
@@ -93,15 +87,8 @@ QString BundleImporter::importComponent(const QString &qmlFile,
} }
FilePath qmldirPath = bundleImportPath.resolvePath(QStringLiteral("qmldir")); FilePath qmldirPath = bundleImportPath.resolvePath(QStringLiteral("qmldir"));
QFile qmldirFile(qmldirPath.toString()); QString qmldirContent = QString::fromUtf8(qmldirPath.fileContents());
if (qmldirContent.isEmpty()) {
QString qmldirContent;
if (qmldirPath.exists()) {
if (!qmldirFile.open(QIODeviceBase::ReadOnly))
return QStringLiteral("Failed to open qmldir file for reading: '%1'").arg(qmldirPath.toString());
qmldirContent = QString::fromUtf8(qmldirFile.readAll());
qmldirFile.close();
} else {
qmldirContent.append("module "); qmldirContent.append("module ");
qmldirContent.append(m_moduleName); qmldirContent.append(m_moduleName);
qmldirContent.append('\n'); qmldirContent.append('\n');
@@ -113,17 +100,11 @@ QString BundleImporter::importComponent(const QString &qmlFile,
m_pendingTypes.append(QStringLiteral("%1.%2") m_pendingTypes.append(QStringLiteral("%1.%2")
.arg(QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1), qmlType)); .arg(QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1), qmlType));
if (!qmldirContent.contains(qmlFile)) { if (!qmldirContent.contains(qmlFile)) {
QSaveFile qmldirSaveFile(qmldirPath.toString());
if (!qmldirSaveFile.open(QIODeviceBase::WriteOnly | QIODeviceBase::Truncate))
return QStringLiteral("Failed to open qmldir file for writing: '%1'").arg(qmldirPath.toString());
qmldirContent.append(qmlType); qmldirContent.append(qmlType);
qmldirContent.append(" 1.0 "); qmldirContent.append(" 1.0 ");
qmldirContent.append(qmlFile); qmldirContent.append(qmlFile);
qmldirContent.append('\n'); qmldirContent.append('\n');
qmldirPath.writeFileContents(qmldirContent.toUtf8());
qmldirSaveFile.write(qmldirContent.toUtf8());
qmldirSaveFile.commit();
} }
QStringList allFiles; QStringList allFiles;
@@ -145,6 +126,19 @@ QString BundleImporter::importComponent(const QString &qmlFile,
return QStringLiteral("Failed to copy file: '%1'").arg(source.toString()); return QStringLiteral("Failed to copy file: '%1'").arg(source.toString());
} }
QVariantHash assetRefMap = loadAssetRefMap(bundleImportPath);
bool writeAssetRefs = false;
for (const QString &assetFile : files) {
QStringList assets = assetRefMap[assetFile].toStringList();
if (!assets.contains(qmlFile)) {
assets.append(qmlFile);
writeAssetRefs = true;
}
assetRefMap[assetFile] = assets;
}
if (writeAssetRefs)
writeAssetRefMap(bundleImportPath, assetRefMap);
m_fullReset = !qmlFileExists; m_fullReset = !qmlFileExists;
auto doc = QmlDesignerPlugin::instance()->currentDesignDocument(); auto doc = QmlDesignerPlugin::instance()->currentDesignDocument();
Model *model = doc ? doc->currentModel() : nullptr; Model *model = doc ? doc->currentModel() : nullptr;
@@ -163,10 +157,12 @@ QString BundleImporter::importComponent(const QString &qmlFile,
} }
} else { } else {
// If import is not yet possible, import statement needs to be added asynchronously to // If import is not yet possible, import statement needs to be added asynchronously to
// avoid errors, as code model update takes a while. Full reset is not necessary // avoid errors, as code model update takes a while.
// in this case, as new import directory appearing will trigger scanning of it.
m_importAddPending = true; m_importAddPending = true;
m_fullReset = false;
// Full reset is not necessary if new import directory appearing will trigger scanning,
// but if directory existed but was not valid possible import, we need to do a reset.
m_fullReset = bundleImportPathExists;
} }
} }
m_importTimerCount = 0; m_importTimerCount = 0;
@@ -232,4 +228,114 @@ void BundleImporter::handleImportTimer()
} }
} }
QVariantHash BundleImporter::loadAssetRefMap(const Utils::FilePath &bundlePath)
{
FilePath assetRefPath = bundlePath.resolvePath(QLatin1String(Constants::COMPONENT_BUNDLES_ASSET_REF_FILE));
QByteArray content = assetRefPath.fileContents();
if (!content.isEmpty()) {
QJsonParseError error;
QJsonDocument bundleDataJsonDoc = QJsonDocument::fromJson(content, &error);
if (bundleDataJsonDoc.isNull()) {
// Failure to read asset refs is not considred fatal, so just print error
qWarning() << "Failed to parse bundle asset ref file:" << error.errorString();
} else {
return bundleDataJsonDoc.object().toVariantHash();
}
}
return {};
}
void BundleImporter::writeAssetRefMap(const Utils::FilePath &bundlePath,
const QVariantHash &assetRefMap)
{
FilePath assetRefPath = bundlePath.resolvePath(QLatin1String(Constants::COMPONENT_BUNDLES_ASSET_REF_FILE));
QJsonObject jsonObj = QJsonObject::fromVariantHash(assetRefMap);
if (!assetRefPath.writeFileContents(QJsonDocument{jsonObj}.toJson())) {
// Failure to write asset refs is not considred fatal, so just print error
qWarning() << QStringLiteral("Failed to save bundle asset ref file: '%1'").arg(assetRefPath.toString()) ;
}
}
QString BundleImporter::unimportComponent(const QString &qmlFile)
{
FilePath bundleImportPath = resolveBundleImportPath();
if (bundleImportPath.isEmpty())
return "Failed to resolve bundle import folder";
if (!bundleImportPath.exists())
return {};
FilePath qmlFilePath = bundleImportPath.resolvePath(qmlFile);
if (!qmlFilePath.exists())
return {};
QStringList removedFiles;
removedFiles.append(qmlFile);
FilePath qmldirPath = bundleImportPath.resolvePath(QStringLiteral("qmldir"));
QByteArray qmldirContent = qmldirPath.fileContents();
QByteArray newContent;
if (!qmldirContent.isEmpty()) {
QByteArray qmlType = qmlFilePath.baseName().toUtf8();
int typeIndex = qmldirContent.indexOf(qmlType);
if (typeIndex != -1) {
int newLineIndex = qmldirContent.indexOf('\n', typeIndex);
newContent = qmldirContent.left(typeIndex);
if (newLineIndex != -1)
newContent.append(qmldirContent.mid(newLineIndex + 1));
}
if (newContent != qmldirContent) {
if (!qmldirPath.writeFileContents(newContent))
return QStringLiteral("Failed to write qmldir file: '%1'").arg(qmldirPath.toString());
}
}
QVariantHash assetRefMap = loadAssetRefMap(bundleImportPath);
bool writeAssetRefs = false;
const auto keys = assetRefMap.keys();
for (const QString &assetFile : keys) {
QStringList assets = assetRefMap[assetFile].toStringList();
if (assets.contains(qmlFile)) {
assets.removeAll(qmlFile);
writeAssetRefs = true;
}
if (!assets.isEmpty()) {
assetRefMap[assetFile] = assets;
} else {
removedFiles.append(assetFile);
assetRefMap.remove(assetFile);
writeAssetRefs = true;
}
}
for (const QString &removedFile : removedFiles) {
FilePath removedFilePath = bundleImportPath.resolvePath(removedFile);
if (removedFilePath.exists())
removedFilePath.removeFile();
}
if (writeAssetRefs)
writeAssetRefMap(bundleImportPath, assetRefMap);
m_fullReset = true;
m_importTimerCount = 0;
m_importTimer.start();
return {};
}
FilePath BundleImporter::resolveBundleImportPath()
{
FilePath bundleImportPath = QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath();
if (bundleImportPath.isEmpty())
return bundleImportPath;
const QString projectBundlePath = QStringLiteral("%1%2/%3").arg(
QLatin1String(Constants::DEFAULT_ASSET_IMPORT_FOLDER),
QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER),
m_bundleId).mid(1); // Chop leading slash
return bundleImportPath.resolvePath(projectBundlePath);
}
} // namespace QmlDesigner::Internal } // namespace QmlDesigner::Internal

View File

@@ -30,6 +30,7 @@
#include "nodemetainfo.h" #include "nodemetainfo.h"
#include <QTimer> #include <QTimer>
#include <QVariantHash>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
QT_END_NAMESPACE QT_END_NAMESPACE
@@ -49,6 +50,8 @@ public:
QString importComponent(const QString &qmlFile, QString importComponent(const QString &qmlFile,
const QStringList &files); const QStringList &files);
QString unimportComponent(const QString &qmlFile);
signals: signals:
// The metaInfo parameter will be invalid if an error was encountered during // The metaInfo parameter will be invalid if an error was encountered during
// asynchronous part of the import. In this case all remaining pending imports have been // asynchronous part of the import. In this case all remaining pending imports have been
@@ -57,6 +60,9 @@ signals:
private: private:
void handleImportTimer(); void handleImportTimer();
QVariantHash loadAssetRefMap(const Utils::FilePath &bundlePath);
void writeAssetRefMap(const Utils::FilePath &bundlePath, const QVariantHash &assetRefMap);
Utils::FilePath resolveBundleImportPath();
Utils::FilePath m_bundleDir; Utils::FilePath m_bundleDir;
QString m_bundleId; QString m_bundleId;

View File

@@ -84,6 +84,7 @@ const char EDIT3D_BACKGROUND_COLOR_ACTIONS[] = "QmlDesigner.Editor3D.BackgroundC
const char QML_DESIGNER_SUBFOLDER[] = "/designer/"; const char QML_DESIGNER_SUBFOLDER[] = "/designer/";
const char COMPONENT_BUNDLES_FOLDER[] = "/ComponentBundles"; const char COMPONENT_BUNDLES_FOLDER[] = "/ComponentBundles";
const char COMPONENT_BUNDLES_ASSET_REF_FILE[] = "_asset_ref.json";
const char QUICK_3D_ASSETS_FOLDER[] = "/Quick3DAssets"; const char QUICK_3D_ASSETS_FOLDER[] = "/Quick3DAssets";
const char QUICK_3D_ASSET_LIBRARY_ICON_SUFFIX[] = "_libicon"; const char QUICK_3D_ASSET_LIBRARY_ICON_SUFFIX[] = "_libicon";
const char QUICK_3D_ASSET_ICON_DIR[] = "_icons"; const char QUICK_3D_ASSET_ICON_DIR[] = "_icons";