feat: support persistent stroage and lastLaunchedTime
Signed-off-by: ComixHe <heyuming@deepin.org>
This commit is contained in:
parent
7b527c609b
commit
23dcd13f10
@ -8,6 +8,7 @@
|
||||
#include <QDir>
|
||||
#include "dbus/applicationmanager1service.h"
|
||||
#include "cgroupsidentifier.h"
|
||||
#include "applicationmanagerstorage.h"
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
|
||||
@ -42,7 +43,10 @@ int main(int argc, char *argv[])
|
||||
auto &AMBus = bus.globalServerBus();
|
||||
|
||||
registerComplexDbusType();
|
||||
ApplicationManager1Service AMService{std::make_unique<CGroupsIdentifier>(), AMBus};
|
||||
auto storageDir = getXDGDataHome() + QDir::separator() + "deepin" + QDir::separator() + "ApplicationManager";
|
||||
auto storage = ApplicationManager1Storage::createApplicationManager1Storage(storageDir);
|
||||
|
||||
ApplicationManager1Service AMService{std::make_unique<CGroupsIdentifier>(), AMBus, storage};
|
||||
|
||||
#ifdef PROFILING_MODE
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
|
205
src/applicationmanagerstorage.cpp
Normal file
205
src/applicationmanagerstorage.cpp
Normal file
@ -0,0 +1,205 @@
|
||||
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
#include "applicationmanagerstorage.h"
|
||||
#include "constant.h"
|
||||
#include <QFileInfo>
|
||||
#include <QJsonDocument>
|
||||
#include <QDir>
|
||||
#include <memory>
|
||||
|
||||
std::shared_ptr<ApplicationManager1Storage>
|
||||
ApplicationManager1Storage::createApplicationManager1Storage(const QString &storageDir) noexcept
|
||||
{
|
||||
QDir dir;
|
||||
auto dirPath = QDir::cleanPath(storageDir);
|
||||
if (!dir.mkpath(dirPath)) {
|
||||
qCritical() << "can't create directory";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
dir.setPath(dirPath);
|
||||
auto storagePath = dir.filePath("storage.json");
|
||||
auto obj = std::shared_ptr<ApplicationManager1Storage>(new (std::nothrow) ApplicationManager1Storage{storagePath});
|
||||
|
||||
if (!obj) {
|
||||
qCritical() << "new ApplicationManager1Storage failed.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!obj->m_file->open(QFile::ReadWrite)) {
|
||||
qCritical() << "can't open file:" << storagePath;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto content = obj->m_file->readAll();
|
||||
if (content.isEmpty()) { // new file
|
||||
obj->setVersion(STORAGE_VERSION);
|
||||
return obj;
|
||||
}
|
||||
|
||||
// TODO: support migrate from lower storage version.
|
||||
|
||||
QJsonParseError err;
|
||||
auto json = QJsonDocument::fromJson(content, &err);
|
||||
if (err.error != QJsonParseError::NoError) {
|
||||
qDebug() << "parse json failed:" << err.errorString() << "clear this file content.";
|
||||
obj->m_file->resize(0);
|
||||
} else {
|
||||
obj->m_data = json.object();
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
ApplicationManager1Storage::ApplicationManager1Storage(const QString &storagePath)
|
||||
: m_file(std::make_unique<QFile>(storagePath))
|
||||
{
|
||||
}
|
||||
|
||||
void ApplicationManager1Storage::writeToFile() const noexcept
|
||||
{
|
||||
if (!m_file) {
|
||||
qCritical() << "file is nullptr";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_file->resize(0)) {
|
||||
qCritical() << "failed to clear file:" << m_file->errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
auto content = QJsonDocument{m_data}.toJson(QJsonDocument::Compact);
|
||||
auto bytes = m_file->write(content, content.size());
|
||||
if (bytes != content.size()) {
|
||||
qWarning() << "Incomplete file writes:" << m_file->errorString();
|
||||
}
|
||||
|
||||
if (!m_file->flush()) {
|
||||
qCritical() << "io error.";
|
||||
}
|
||||
}
|
||||
|
||||
void ApplicationManager1Storage::setVersion(uint8_t version) noexcept
|
||||
{
|
||||
m_data["version"] = version;
|
||||
writeToFile();
|
||||
}
|
||||
|
||||
uint8_t ApplicationManager1Storage::version() const noexcept
|
||||
{
|
||||
return m_data["version"].toInt(0);
|
||||
}
|
||||
|
||||
void ApplicationManager1Storage::createApplicationValue(const QString &appId,
|
||||
const QString &groupName,
|
||||
const QString &valueKey,
|
||||
const QVariant &value) noexcept
|
||||
{
|
||||
if (appId.isEmpty() or groupName.isEmpty() or valueKey.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject appObj;
|
||||
if (m_data.contains(appId)) {
|
||||
appObj = m_data[appId].toObject();
|
||||
}
|
||||
|
||||
QJsonObject groupObj;
|
||||
if (appObj.contains(groupName)) {
|
||||
groupObj = appObj[groupName].toObject();
|
||||
}
|
||||
|
||||
if (groupObj.contains(valueKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
groupObj.insert(valueKey, value.toJsonValue());
|
||||
appObj.insert(groupName, groupObj);
|
||||
m_data.insert(appId, appObj);
|
||||
|
||||
writeToFile();
|
||||
}
|
||||
|
||||
void ApplicationManager1Storage::updateApplicationValue(const QString &appId,
|
||||
const QString &groupName,
|
||||
const QString &valueKey,
|
||||
const QVariant &value) noexcept
|
||||
{
|
||||
if (appId.isEmpty() or groupName.isEmpty() or valueKey.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_data.contains(appId)) {
|
||||
return;
|
||||
}
|
||||
auto appObj = m_data[appId].toObject();
|
||||
|
||||
if (!appObj.contains(groupName)) {
|
||||
return;
|
||||
}
|
||||
auto groupObj = appObj[groupName].toObject();
|
||||
|
||||
if (!groupObj.contains(valueKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
groupObj.insert(valueKey, value.toJsonValue());
|
||||
appObj.insert(groupName, groupObj);
|
||||
m_data.insert(appId, appObj);
|
||||
|
||||
writeToFile();
|
||||
}
|
||||
|
||||
QVariant ApplicationManager1Storage::readApplicationValue(const QString &appId,
|
||||
const QString &groupName,
|
||||
const QString &valueKey) const noexcept
|
||||
{
|
||||
return m_data[appId][groupName][valueKey].toVariant();
|
||||
}
|
||||
|
||||
void ApplicationManager1Storage::deleteApplicationValue(const QString &appId,
|
||||
const QString &groupName,
|
||||
const QString &valueKey) noexcept
|
||||
{
|
||||
if (appId.isEmpty()) {
|
||||
auto empty = QJsonObject{};
|
||||
m_data.swap(empty);
|
||||
return;
|
||||
}
|
||||
|
||||
auto app = m_data.find(appId).value();
|
||||
if (app.isNull()) {
|
||||
return;
|
||||
}
|
||||
auto appObj = app.toObject();
|
||||
|
||||
if (groupName.isEmpty()) {
|
||||
m_data.remove(appId);
|
||||
return;
|
||||
}
|
||||
|
||||
auto group = appObj.find(groupName).value();
|
||||
if (group.isNull()) {
|
||||
return;
|
||||
}
|
||||
auto groupObj = group.toObject();
|
||||
|
||||
if (valueKey.isEmpty()) {
|
||||
appObj.remove(groupName);
|
||||
m_data.insert(appId, appObj);
|
||||
return;
|
||||
}
|
||||
|
||||
auto val = groupObj.find(valueKey).value();
|
||||
if (val.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
groupObj.remove(valueKey);
|
||||
appObj.insert(groupName, groupObj);
|
||||
m_data.insert(appId, appObj);
|
||||
|
||||
writeToFile();
|
||||
}
|
47
src/applicationmanagerstorage.h
Normal file
47
src/applicationmanagerstorage.h
Normal file
@ -0,0 +1,47 @@
|
||||
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
#ifndef APPLICATIONMANAGERSTORAGE_H
|
||||
#define APPLICATIONMANAGERSTORAGE_H
|
||||
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
#include <QFile>
|
||||
|
||||
enum class ModifyMode { Create, Update };
|
||||
|
||||
class ApplicationManager1Storage
|
||||
{
|
||||
public:
|
||||
ApplicationManager1Storage(const ApplicationManager1Storage &) = delete;
|
||||
ApplicationManager1Storage(ApplicationManager1Storage &&) = default;
|
||||
ApplicationManager1Storage &operator=(const ApplicationManager1Storage &) = delete;
|
||||
ApplicationManager1Storage &operator=(ApplicationManager1Storage &&) = default;
|
||||
~ApplicationManager1Storage() = default;
|
||||
|
||||
void createApplicationValue(const QString &appId,
|
||||
const QString &groupName,
|
||||
const QString &valueKey,
|
||||
const QVariant &value) noexcept;
|
||||
void updateApplicationValue(const QString &appId,
|
||||
const QString &groupName,
|
||||
const QString &valueKey,
|
||||
const QVariant &value) noexcept;
|
||||
[[nodiscard]] QVariant
|
||||
readApplicationValue(const QString &appId, const QString &groupName, const QString &valueKey) const noexcept;
|
||||
void deleteApplicationValue(const QString &appId = "", const QString &groupName = "", const QString &valueKey = "") noexcept;
|
||||
|
||||
void setVersion(uint8_t version) noexcept;
|
||||
[[nodiscard]] uint8_t version() const noexcept;
|
||||
|
||||
static std::shared_ptr<ApplicationManager1Storage> createApplicationManager1Storage(const QString &storageDir) noexcept;
|
||||
|
||||
private:
|
||||
void writeToFile() const noexcept;
|
||||
explicit ApplicationManager1Storage(const QString &storagePath);
|
||||
std::unique_ptr<QFile> m_file;
|
||||
QJsonObject m_data;
|
||||
};
|
||||
|
||||
#endif
|
@ -47,4 +47,8 @@ constexpr auto systemdOption = u8"systemd";
|
||||
constexpr auto splitOption = u8"split";
|
||||
constexpr auto AppExecOption = u8"appExec";
|
||||
|
||||
constexpr auto STORAGE_VERSION = 1;
|
||||
constexpr auto ApplicationPropertiesGroup = u8"Application Properties";
|
||||
constexpr auto LastLaunchedTime = u8"LastLaunchedTime";
|
||||
|
||||
#endif
|
||||
|
@ -13,8 +13,11 @@
|
||||
|
||||
ApplicationManager1Service::~ApplicationManager1Service() = default;
|
||||
|
||||
ApplicationManager1Service::ApplicationManager1Service(std::unique_ptr<Identifier> ptr, QDBusConnection &connection) noexcept
|
||||
ApplicationManager1Service::ApplicationManager1Service(std::unique_ptr<Identifier> ptr,
|
||||
QDBusConnection &connection,
|
||||
std::weak_ptr<ApplicationManager1Storage> storage) noexcept
|
||||
: m_identifier(std::move(ptr))
|
||||
, m_storage(std::move(storage))
|
||||
{
|
||||
if (!connection.registerService(DDEApplicationManager1ServiceName)) {
|
||||
qFatal("%s", connection.lastError().message().toLocal8Bit().data());
|
||||
@ -116,6 +119,17 @@ void ApplicationManager1Service::addInstanceToApplication(const QString &unitNam
|
||||
return;
|
||||
}
|
||||
|
||||
if (sender() != nullptr) { // activate by signal
|
||||
auto timestamp = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
if (auto ptr = m_storage.lock(); ptr) {
|
||||
ptr->updateApplicationValue((*appIt)->m_desktopSource.desktopId(),
|
||||
ApplicationPropertiesGroup,
|
||||
LastLaunchedTime,
|
||||
QVariant::fromValue(timestamp));
|
||||
}
|
||||
}
|
||||
|
||||
const auto &applicationPath = (*appIt)->applicationPath().path();
|
||||
|
||||
if (!(*appIt)->addOneInstance(instanceId, applicationPath, systemdUnitPath.path())) [[likely]] {
|
||||
@ -229,7 +243,7 @@ QList<QDBusObjectPath> ApplicationManager1Service::list() const
|
||||
bool ApplicationManager1Service::addApplication(DesktopFile desktopFileSource) noexcept
|
||||
{
|
||||
QSharedPointer<ApplicationService> application =
|
||||
ApplicationService::createApplicationService(std::move(desktopFileSource), this);
|
||||
ApplicationService::createApplicationService(std::move(desktopFileSource), this, m_storage);
|
||||
if (!application) {
|
||||
return false;
|
||||
}
|
||||
@ -251,6 +265,18 @@ bool ApplicationManager1Service::addApplication(DesktopFile desktopFileSource) n
|
||||
return false;
|
||||
}
|
||||
m_applicationList.insert(application->applicationPath(), application);
|
||||
|
||||
if (auto storagePtr = m_storage.lock(); storagePtr) {
|
||||
auto appId = ptr->id();
|
||||
auto value = storagePtr->readApplicationValue(appId, ApplicationPropertiesGroup, LastLaunchedTime);
|
||||
if (value.isNull()) {
|
||||
storagePtr->createApplicationValue(
|
||||
appId, ApplicationPropertiesGroup, LastLaunchedTime, QVariant::fromValue<qint64>(0));
|
||||
} else {
|
||||
ptr->m_lastLaunch = value.toInt();
|
||||
}
|
||||
}
|
||||
|
||||
emit listChanged();
|
||||
emit InterfacesAdded(application->applicationPath(), getChildInterfacesAndPropertiesFromObject(ptr));
|
||||
|
||||
@ -261,8 +287,12 @@ void ApplicationManager1Service::removeOneApplication(const QDBusObjectPath &app
|
||||
{
|
||||
if (auto it = m_applicationList.find(application); it != m_applicationList.cend()) {
|
||||
emit InterfacesRemoved(application, getChildInterfacesFromObject(it->data()));
|
||||
if (auto ptr = m_storage.lock(); ptr) {
|
||||
ptr->deleteApplicationValue((*it)->id());
|
||||
}
|
||||
unregisterObjectFromDBus(application.path());
|
||||
m_applicationList.remove(application);
|
||||
|
||||
emit listChanged();
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <QScopedPointer>
|
||||
#include <memory>
|
||||
#include <QMap>
|
||||
#include "applicationmanagerstorage.h"
|
||||
#include "dbus/jobmanager1service.h"
|
||||
#include "desktopentry.h"
|
||||
#include "identifier.h"
|
||||
@ -22,7 +23,9 @@ class ApplicationManager1Service final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ApplicationManager1Service(std::unique_ptr<Identifier> ptr, QDBusConnection &connection) noexcept;
|
||||
explicit ApplicationManager1Service(std::unique_ptr<Identifier> ptr,
|
||||
QDBusConnection &connection,
|
||||
std::weak_ptr<ApplicationManager1Storage> storage) noexcept;
|
||||
~ApplicationManager1Service() override;
|
||||
ApplicationManager1Service(const ApplicationManager1Service &) = delete;
|
||||
ApplicationManager1Service(ApplicationManager1Service &&) = delete;
|
||||
@ -52,6 +55,7 @@ Q_SIGNALS:
|
||||
|
||||
private:
|
||||
std::unique_ptr<Identifier> m_identifier;
|
||||
std::weak_ptr<ApplicationManager1Storage> m_storage;
|
||||
QScopedPointer<JobManager1Service> m_jobManager{nullptr};
|
||||
QMap<QDBusObjectPath, QSharedPointer<ApplicationService>> m_applicationList;
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "APPobjectmanager1adaptor.h"
|
||||
#include "applicationchecker.h"
|
||||
#include "applicationmanager1service.h"
|
||||
#include "applicationmanagerstorage.h"
|
||||
#include "propertiesForwarder.h"
|
||||
#include "dbus/instanceadaptor.h"
|
||||
#include "launchoptions.h"
|
||||
@ -18,10 +19,14 @@
|
||||
#include <QStandardPaths>
|
||||
#include <algorithm>
|
||||
#include <new>
|
||||
#include <utility>
|
||||
#include <wordexp.h>
|
||||
|
||||
ApplicationService::ApplicationService(DesktopFile source, ApplicationManager1Service *parent)
|
||||
ApplicationService::ApplicationService(DesktopFile source,
|
||||
ApplicationManager1Service *parent,
|
||||
std::weak_ptr<ApplicationManager1Storage> storage)
|
||||
: QObject(parent)
|
||||
, m_storage(std::move(storage))
|
||||
, m_desktopSource(std::move(source))
|
||||
{
|
||||
}
|
||||
@ -36,10 +41,10 @@ ApplicationService::~ApplicationService()
|
||||
}
|
||||
}
|
||||
|
||||
QSharedPointer<ApplicationService> ApplicationService::createApplicationService(DesktopFile source,
|
||||
ApplicationManager1Service *parent) noexcept
|
||||
QSharedPointer<ApplicationService> ApplicationService::createApplicationService(
|
||||
DesktopFile source, ApplicationManager1Service *parent, std::weak_ptr<ApplicationManager1Storage> storage) noexcept
|
||||
{
|
||||
QSharedPointer<ApplicationService> app{new (std::nothrow) ApplicationService{std::move(source), parent}};
|
||||
QSharedPointer<ApplicationService> app{new (std::nothrow) ApplicationService{std::move(source), parent, std::move(storage)}};
|
||||
if (!app) {
|
||||
qCritical() << "new application service failed.";
|
||||
return nullptr;
|
||||
@ -90,6 +95,12 @@ QSharedPointer<ApplicationService> ApplicationService::createApplicationService(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto ptr = app->m_storage.lock();
|
||||
if (!ptr) {
|
||||
qWarning() << "runtime storage doesn't exists.";
|
||||
return app;
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
@ -176,7 +187,7 @@ QDBusObjectPath ApplicationService::Launch(const QString &action, const QStringL
|
||||
auto &jobManager = static_cast<ApplicationManager1Service *>(parent())->jobManager();
|
||||
return jobManager.addJob(
|
||||
m_applicationPath.path(),
|
||||
[this, binary = std::move(bin), commands = std::move(cmds)](QVariant variantValue) mutable -> QVariant {
|
||||
[this, binary = std::move(bin), commands = std::move(cmds)](const QVariant &variantValue) mutable -> QVariant {
|
||||
auto resourceFile = variantValue.toString();
|
||||
auto instanceRandomUUID = QUuid::createUuid().toString(QUuid::Id128);
|
||||
auto objectPath = m_applicationPath.path() + "/" + instanceRandomUUID;
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include <QFile>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include "applicationmanagerstorage.h"
|
||||
#include "dbus/instanceservice.h"
|
||||
#include "global.h"
|
||||
#include "desktopentry.h"
|
||||
@ -110,10 +111,13 @@ Q_SIGNALS:
|
||||
|
||||
private:
|
||||
friend class ApplicationManager1Service;
|
||||
explicit ApplicationService(DesktopFile source, ApplicationManager1Service *parent);
|
||||
static QSharedPointer<ApplicationService> createApplicationService(DesktopFile source,
|
||||
ApplicationManager1Service *parent) noexcept;
|
||||
qlonglong m_lastLaunch{0};
|
||||
explicit ApplicationService(DesktopFile source,
|
||||
ApplicationManager1Service *parent,
|
||||
std::weak_ptr<ApplicationManager1Storage> storage);
|
||||
static QSharedPointer<ApplicationService> createApplicationService(
|
||||
DesktopFile source, ApplicationManager1Service *parent, std::weak_ptr<ApplicationManager1Storage> storage) noexcept;
|
||||
qint64 m_lastLaunch{0};
|
||||
std::weak_ptr<ApplicationManager1Storage> m_storage;
|
||||
QDBusObjectPath m_applicationPath;
|
||||
QString m_launcher{getApplicationLauncherBinary()};
|
||||
DesktopFile m_desktopSource;
|
||||
|
16
src/global.h
16
src/global.h
@ -363,6 +363,15 @@ inline QString getRelativePathFromAppId(const QString &id)
|
||||
return path;
|
||||
}
|
||||
|
||||
inline QString getXDGDataHome()
|
||||
{
|
||||
auto XDGDataHome = QString::fromLocal8Bit(qgetenv("XDG_DATA_HOME"));
|
||||
if (XDGDataHome.isEmpty()) {
|
||||
XDGDataHome = QString::fromLocal8Bit(qgetenv("HOME")) + QDir::separator() + ".local" + QDir::separator() + "share";
|
||||
}
|
||||
return XDGDataHome;
|
||||
}
|
||||
|
||||
inline QStringList getDesktopFileDirs()
|
||||
{
|
||||
auto XDGDataDirs = QString::fromLocal8Bit(qgetenv("XDG_DATA_DIRS")).split(':', Qt::SkipEmptyParts);
|
||||
@ -372,12 +381,7 @@ inline QStringList getDesktopFileDirs()
|
||||
XDGDataDirs.append("/usr/share");
|
||||
}
|
||||
|
||||
auto XDGDataHome = QString::fromLocal8Bit(qgetenv("XDG_DATA_HOME"));
|
||||
if (XDGDataHome.isEmpty()) {
|
||||
XDGDataHome = QString::fromLocal8Bit(qgetenv("HOME")) + QDir::separator() + ".local" + QDir::separator() + "share";
|
||||
}
|
||||
|
||||
XDGDataDirs.push_front(std::move(XDGDataHome));
|
||||
XDGDataDirs.push_front(getXDGDataHome());
|
||||
|
||||
std::for_each(XDGDataDirs.begin(), XDGDataDirs.end(), [](QString &str) {
|
||||
if (!str.endsWith(QDir::separator())) {
|
||||
|
@ -33,11 +33,11 @@ public:
|
||||
auto &bus = ApplicationManager1DBus::instance();
|
||||
bus.initGlobalServerBus(DBusType::Session);
|
||||
bus.setDestBus();
|
||||
|
||||
m_am = new ApplicationManager1Service{std::make_unique<CGroupsIdentifier>(), bus.globalServerBus()};
|
||||
std::shared_ptr<ApplicationManager1Storage> tmp{nullptr};
|
||||
m_am = new ApplicationManager1Service{std::make_unique<CGroupsIdentifier>(), bus.globalServerBus(), tmp};
|
||||
auto ptr = std::make_unique<QFile>(QString{"/usr/share/applications/test-Application.desktop"});
|
||||
DesktopFile file{std::move(ptr), "test-Application", 0, 0};
|
||||
QSharedPointer<ApplicationService> app = QSharedPointer<ApplicationService>::create(std::move(file), nullptr);
|
||||
QSharedPointer<ApplicationService> app = QSharedPointer<ApplicationService>::create(std::move(file), nullptr, tmp);
|
||||
QSharedPointer<InstanceService> instance =
|
||||
QSharedPointer<InstanceService>::create(InstancePath.path().split('/').last(), ApplicationPath.path(), QString{"/"});
|
||||
app->m_Instances.insert(InstancePath, instance);
|
||||
|
Loading…
Reference in New Issue
Block a user