@@ -24,3 +24,30 @@ ly_add_target(
|
||||
3rdParty::AWSNativeSDK::Core
|
||||
AZ::AzCore
|
||||
)
|
||||
|
||||
################################################################################
|
||||
# Tests
|
||||
################################################################################
|
||||
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
ly_add_target(
|
||||
NAME AWSNativeSDKInit.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
|
||||
NAMESPACE AZ
|
||||
FILES_CMAKE
|
||||
aws_native_sdk_init_tests_files.cmake
|
||||
INCLUDE_DIRECTORIES
|
||||
PRIVATE
|
||||
include
|
||||
tests
|
||||
source
|
||||
BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
AZ::AzCore
|
||||
AZ::AzFramework
|
||||
AZ::AzTest
|
||||
AZ::AWSNativeSDKInit
|
||||
3rdParty::AWSNativeSDK::Core
|
||||
)
|
||||
ly_add_googletest(
|
||||
NAME AZ::AWSNativeSDKInit.Tests
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
|
||||
set(FILES
|
||||
tests/AWSLogSystemInterfaceTest.cpp
|
||||
tests/AWSNativeSDKInitTest.cpp
|
||||
)
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <AWSNativeSDKInit/AWSLogSystemInterface.h>
|
||||
|
||||
#include <AzCore/base.h>
|
||||
#include <AzCore/Console/IConsole.h>
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/Module/Environment.h>
|
||||
|
||||
#include <stdarg.h>
|
||||
@@ -24,6 +26,9 @@ AZ_POP_DISABLE_WARNING
|
||||
|
||||
namespace AWSNativeSDKInit
|
||||
{
|
||||
AZ_CVAR(int, bg_awsLogLevel, -1, nullptr, AZ::ConsoleFunctorFlags::Null,
|
||||
"AWSLogLevel used to control verbosity of logging system. Off = 0, Fatal = 1, Error = 2, Warn = 3, Info = 4, Debug = 5, Trace = 6");
|
||||
|
||||
const char* AWSLogSystemInterface::AWS_API_LOG_PREFIX = "AwsApi-";
|
||||
const int AWSLogSystemInterface::MAX_MESSAGE_LENGTH = 4096;
|
||||
const char* AWSLogSystemInterface::MESSAGE_FORMAT = "[AWS] %s - %s";
|
||||
@@ -40,15 +45,16 @@ namespace AWSNativeSDKInit
|
||||
Aws::Utils::Logging::LogLevel AWSLogSystemInterface::GetLogLevel() const
|
||||
{
|
||||
Aws::Utils::Logging::LogLevel newLevel = m_logLevel;
|
||||
static const char* const logLevelEnvVar = "sys_SetLogLevel";
|
||||
auto logVar = AZ::Environment::FindVariable<int>(logLevelEnvVar);
|
||||
|
||||
if (logVar)
|
||||
if (auto console = AZ::Interface<AZ::IConsole>::Get(); console != nullptr)
|
||||
{
|
||||
newLevel = (Aws::Utils::Logging::LogLevel) *logVar;
|
||||
int awsLogLevel = -1;
|
||||
console->GetCvarValue("bg_awsLogLevel", awsLogLevel);
|
||||
if (awsLogLevel >= 0)
|
||||
{
|
||||
newLevel = static_cast<Aws::Utils::Logging::LogLevel>(awsLogLevel);
|
||||
}
|
||||
}
|
||||
|
||||
return newLevel != m_logLevel ? newLevel : m_logLevel;
|
||||
return newLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,14 +84,12 @@ namespace AWSNativeSDKInit
|
||||
*/
|
||||
void AWSLogSystemInterface::LogStream(Aws::Utils::Logging::LogLevel logLevel, const char* tag, const Aws::OStringStream &messageStream)
|
||||
{
|
||||
|
||||
if(!ShouldLog(logLevel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ForwardAwsApiLogMessage(logLevel, tag, messageStream.str().c_str());
|
||||
|
||||
}
|
||||
|
||||
bool AWSLogSystemInterface::ShouldLog(Aws::Utils::Logging::LogLevel logLevel)
|
||||
@@ -93,7 +97,7 @@ namespace AWSNativeSDKInit
|
||||
#if defined(PLATFORM_SUPPORTS_AWS_NATIVE_SDK)
|
||||
Aws::Utils::Logging::LogLevel newLevel = GetLogLevel();
|
||||
|
||||
if (newLevel > Aws::Utils::Logging::LogLevel::Info && newLevel <= Aws::Utils::Logging::LogLevel::Trace && newLevel != m_logLevel)
|
||||
if (newLevel != m_logLevel)
|
||||
{
|
||||
SetLogLevel(newLevel);
|
||||
}
|
||||
@@ -124,7 +128,7 @@ namespace AWSNativeSDKInit
|
||||
break;
|
||||
|
||||
case Aws::Utils::Logging::LogLevel::Error:
|
||||
AZ::Debug::Trace::Instance().Warning(__FILE__, __LINE__, AZ_FUNCTION_SIGNATURE, AWSLogSystemInterface::ERROR_WINDOW_NAME, MESSAGE_FORMAT, tag, message);
|
||||
AZ::Debug::Trace::Instance().Error(__FILE__, __LINE__, AZ_FUNCTION_SIGNATURE, AWSLogSystemInterface::ERROR_WINDOW_NAME, MESSAGE_FORMAT, tag, message);
|
||||
break;
|
||||
|
||||
case Aws::Utils::Logging::LogLevel::Warn:
|
||||
|
||||
@@ -64,10 +64,10 @@ namespace AWSNativeSDKInit
|
||||
{
|
||||
#if defined(PLATFORM_SUPPORTS_AWS_NATIVE_SDK)
|
||||
Aws::Utils::Logging::LogLevel logLevel;
|
||||
#ifdef _DEBUG
|
||||
#if defined(AZ_DEBUG_BUILD) || defined(AZ_PROFILE_BUILD)
|
||||
logLevel = Aws::Utils::Logging::LogLevel::Warn;
|
||||
#else
|
||||
logLevel = Aws::Utils::Logging::LogLevel::Warn;
|
||||
logLevel = Aws::Utils::Logging::LogLevel::Error;
|
||||
#endif
|
||||
m_awsSDKOptions.loggingOptions.logLevel = logLevel;
|
||||
m_awsSDKOptions.loggingOptions.logger_create_fn = [logLevel]()
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Console/Console.h>
|
||||
#include <AzCore/Console/IConsole.h>
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
|
||||
#include <AWSNativeSDKInit/AWSLogSystemInterface.h>
|
||||
|
||||
#include <aws/core/utils/logging/LogLevel.h>
|
||||
|
||||
using namespace AWSNativeSDKInit;
|
||||
|
||||
class AWSLogSystemInterfaceTest
|
||||
: public UnitTest::ScopedAllocatorSetupFixture
|
||||
, public AZ::Debug::TraceMessageBus::Handler
|
||||
{
|
||||
public:
|
||||
bool OnPreAssert(const char*, int, const char*, const char*) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OnPreError(const char*, const char*, int, const char*, const char*) override
|
||||
{
|
||||
m_error = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OnPreWarning(const char*, const char*, int, const char*, const char*) override
|
||||
{
|
||||
m_warning = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OnPrintf(const char*, const char*) override
|
||||
{
|
||||
m_printf = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
BusConnect();
|
||||
if (!AZ::Interface<AZ::IConsole>::Get())
|
||||
{
|
||||
m_console = AZStd::make_unique<AZ::Console>();
|
||||
m_console->LinkDeferredFunctors(AZ::ConsoleFunctorBase::GetDeferredHead());
|
||||
AZ::Interface<AZ::IConsole>::Register(m_console.get());
|
||||
}
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
if (m_console)
|
||||
{
|
||||
AZ::Interface<AZ::IConsole>::Unregister(m_console.get());
|
||||
m_console.reset();
|
||||
}
|
||||
BusDisconnect();
|
||||
}
|
||||
|
||||
bool m_error = false;
|
||||
bool m_warning = false;
|
||||
bool m_printf = false;
|
||||
|
||||
private:
|
||||
AZStd::unique_ptr<AZ::Console> m_console;
|
||||
};
|
||||
|
||||
TEST_F(AWSLogSystemInterfaceTest, LogStream_LogFatalMessage_GetExpectedNotification)
|
||||
{
|
||||
AWSLogSystemInterface logSystem(Aws::Utils::Logging::LogLevel::Trace);
|
||||
Aws::OStringStream testString;
|
||||
logSystem.LogStream(Aws::Utils::Logging::LogLevel::Fatal, "test", testString);
|
||||
ASSERT_TRUE(m_error);
|
||||
ASSERT_FALSE(m_warning);
|
||||
ASSERT_FALSE(m_printf);
|
||||
}
|
||||
|
||||
TEST_F(AWSLogSystemInterfaceTest, LogStream_LogErrorMessage_GetExpectedNotification)
|
||||
{
|
||||
AWSLogSystemInterface logSystem(Aws::Utils::Logging::LogLevel::Trace);
|
||||
Aws::OStringStream testString;
|
||||
logSystem.LogStream(Aws::Utils::Logging::LogLevel::Error, "test", testString);
|
||||
ASSERT_TRUE(m_error);
|
||||
ASSERT_FALSE(m_warning);
|
||||
ASSERT_FALSE(m_printf);
|
||||
}
|
||||
|
||||
TEST_F(AWSLogSystemInterfaceTest, LogStream_LogWarningMessage_GetExpectedNotification)
|
||||
{
|
||||
AWSLogSystemInterface logSystem(Aws::Utils::Logging::LogLevel::Trace);
|
||||
Aws::OStringStream testString;
|
||||
logSystem.LogStream(Aws::Utils::Logging::LogLevel::Warn, "test", testString);
|
||||
ASSERT_FALSE(m_error);
|
||||
ASSERT_TRUE(m_warning);
|
||||
ASSERT_FALSE(m_printf);
|
||||
}
|
||||
|
||||
TEST_F(AWSLogSystemInterfaceTest, LogStream_LogInfoMessage_GetExpectedNotification)
|
||||
{
|
||||
AWSLogSystemInterface logSystem(Aws::Utils::Logging::LogLevel::Trace);
|
||||
Aws::OStringStream testString;
|
||||
logSystem.LogStream(Aws::Utils::Logging::LogLevel::Info, "test", testString);
|
||||
ASSERT_FALSE(m_error);
|
||||
ASSERT_FALSE(m_warning);
|
||||
ASSERT_TRUE(m_printf);
|
||||
}
|
||||
|
||||
TEST_F(AWSLogSystemInterfaceTest, LogStream_LogDebugMessage_GetExpectedNotification)
|
||||
{
|
||||
AWSLogSystemInterface logSystem(Aws::Utils::Logging::LogLevel::Trace);
|
||||
Aws::OStringStream testString;
|
||||
logSystem.LogStream(Aws::Utils::Logging::LogLevel::Debug, "test", testString);
|
||||
ASSERT_FALSE(m_error);
|
||||
ASSERT_FALSE(m_warning);
|
||||
ASSERT_TRUE(m_printf);
|
||||
}
|
||||
|
||||
TEST_F(AWSLogSystemInterfaceTest, LogStream_LogTraceMessage_GetExpectedNotification)
|
||||
{
|
||||
AWSLogSystemInterface logSystem(Aws::Utils::Logging::LogLevel::Trace);
|
||||
Aws::OStringStream testString;
|
||||
logSystem.LogStream(Aws::Utils::Logging::LogLevel::Trace, "test", testString);
|
||||
ASSERT_FALSE(m_error);
|
||||
ASSERT_FALSE(m_warning);
|
||||
ASSERT_TRUE(m_printf);
|
||||
}
|
||||
|
||||
TEST_F(AWSLogSystemInterfaceTest, LogStream_OverrideWarnAndLogInfoMessage_GetExpectedNotification)
|
||||
{
|
||||
AWSLogSystemInterface logSystem(Aws::Utils::Logging::LogLevel::Trace);
|
||||
Aws::OStringStream testString;
|
||||
AZ::Interface<AZ::IConsole>::Get()->PerformCommand("bg_awsLogLevel 3");
|
||||
logSystem.LogStream(Aws::Utils::Logging::LogLevel::Info, "test", testString);
|
||||
ASSERT_FALSE(m_error);
|
||||
ASSERT_FALSE(m_warning);
|
||||
ASSERT_FALSE(m_printf);
|
||||
}
|
||||
|
||||
TEST_F(AWSLogSystemInterfaceTest, LogStream_OverrideWarnAndLogeErrorMessage_GetExpectedNotification)
|
||||
{
|
||||
AWSLogSystemInterface logSystem(Aws::Utils::Logging::LogLevel::Trace);
|
||||
Aws::OStringStream testString;
|
||||
AZ::Interface<AZ::IConsole>::Get()->PerformCommand("bg_awsLogLevel 3");
|
||||
logSystem.LogStream(Aws::Utils::Logging::LogLevel::Error, "test", testString);
|
||||
ASSERT_TRUE(m_error);
|
||||
ASSERT_FALSE(m_warning);
|
||||
ASSERT_FALSE(m_printf);
|
||||
}
|
||||
|
||||
TEST_F(AWSLogSystemInterfaceTest, LogStream_OverrideOffAndLogInfoMessage_GetExpectedNotification)
|
||||
{
|
||||
AWSLogSystemInterface logSystem(Aws::Utils::Logging::LogLevel::Trace);
|
||||
Aws::OStringStream testString;
|
||||
AZ::Interface<AZ::IConsole>::Get()->PerformCommand("bg_awsLogLevel 0");
|
||||
logSystem.LogStream(Aws::Utils::Logging::LogLevel::Info, "test", testString);
|
||||
ASSERT_FALSE(m_error);
|
||||
ASSERT_FALSE(m_warning);
|
||||
ASSERT_FALSE(m_printf);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzTest/AzTest.h>
|
||||
|
||||
AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);
|
||||
@@ -77,6 +77,10 @@ ly_add_target(
|
||||
${additional_dependencies}
|
||||
)
|
||||
|
||||
if(LY_DEFAULT_PROJECT_PATH)
|
||||
set_property(TARGET AssetBundler AssetBundlerBatch APPEND PROPERTY VS_DEBUGGER_COMMAND_ARGUMENTS "--project-path=\"${LY_DEFAULT_PROJECT_PATH}\"")
|
||||
endif()
|
||||
|
||||
# Adds a specialized .setreg to identify gems enabled in the active project.
|
||||
# This associates the AssetBundler target with the .Builders gem variants.
|
||||
ly_set_gem_variant_to_load(TARGETS AssetBundler VARIANTS Builders)
|
||||
|
||||
@@ -346,9 +346,7 @@ namespace AssetBundler
|
||||
}
|
||||
|
||||
// Determine the enabled platforms
|
||||
const char* appRoot = nullptr;
|
||||
AzFramework::ApplicationRequests::Bus::BroadcastResult(appRoot, &AzFramework::ApplicationRequests::GetAppRoot);
|
||||
m_enabledPlatforms = GetEnabledPlatformFlags(GetEngineRoot(), appRoot, AZ::Utils::GetProjectPath().c_str());
|
||||
m_enabledPlatforms = GetEnabledPlatformFlags(GetEngineRoot(), AZStd::string_view(AZ::Utils::GetProjectPath()));
|
||||
|
||||
// Determine which Gems are enabled for the current project
|
||||
if (!AzFramework::GetGemsInfo(m_gemInfoList, *m_settingsRegistry))
|
||||
|
||||
@@ -1401,7 +1401,6 @@ namespace AssetBundler
|
||||
|
||||
// If no platform was specified, defaulting to platforms specified in the asset processor config files
|
||||
AzFramework::PlatformFlags platformFlags = GetEnabledPlatformFlags(
|
||||
AZStd::string_view{ AZ::Utils::GetEnginePath() },
|
||||
AZStd::string_view{ AZ::Utils::GetEnginePath() },
|
||||
AZStd::string_view{ AZ::Utils::GetProjectPath() });
|
||||
[[maybe_unused]] auto platformsString = AzFramework::PlatformHelper::GetCommaSeparatedPlatformList(platformFlags);
|
||||
|
||||
@@ -377,7 +377,6 @@ namespace AssetBundler
|
||||
|
||||
AzFramework::PlatformFlags GetEnabledPlatformFlags(
|
||||
AZStd::string_view engineRoot,
|
||||
AZStd::string_view assetRoot,
|
||||
AZStd::string_view projectPath)
|
||||
{
|
||||
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
@@ -387,7 +386,7 @@ namespace AssetBundler
|
||||
return AzFramework::PlatformFlags::Platform_NONE;
|
||||
}
|
||||
|
||||
auto configFiles = AzToolsFramework::AssetUtils::GetConfigFiles(engineRoot, assetRoot, projectPath, true, true, settingsRegistry);
|
||||
auto configFiles = AzToolsFramework::AssetUtils::GetConfigFiles(engineRoot, projectPath, true, true, settingsRegistry);
|
||||
auto enabledPlatformList = AzToolsFramework::AssetUtils::GetEnabledPlatforms(*settingsRegistry, configFiles);
|
||||
AzFramework::PlatformFlags platformFlags = AzFramework::PlatformFlags::Platform_NONE;
|
||||
for (const auto& enabledPlatform : enabledPlatformList)
|
||||
|
||||
@@ -221,7 +221,6 @@ namespace AssetBundler
|
||||
//! Please note that the game project could be in a different location to the engine therefore we need the assetRoot param.
|
||||
AzFramework::PlatformFlags GetEnabledPlatformFlags(
|
||||
AZStd::string_view enginePath,
|
||||
AZStd::string_view assetRoot,
|
||||
AZStd::string_view projectPath);
|
||||
|
||||
QJsonObject ReadJson(const AZStd::string& filePath);
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace AssetBundler
|
||||
void NormalizePathKeepCase(AZStd::string& /*path*/) override {}
|
||||
void CalculateBranchTokenForEngineRoot(AZStd::string& /*token*/) const override {}
|
||||
|
||||
const char* GetEngineRoot() const override
|
||||
const char* GetTempDir() const
|
||||
{
|
||||
return m_tempDir->GetDirectory();
|
||||
}
|
||||
@@ -83,7 +83,7 @@ namespace AssetBundler
|
||||
TEST_F(MockUtilsTest, DISABLED_TestFilePath_StartsWithAFileSeparator_Valid)
|
||||
{
|
||||
AZ::IO::Path relFilePath = "Foo/foo.xml";
|
||||
AZ::IO::Path absoluteFilePath = AZ::IO::PathView(GetEngineRoot()).RootPath();
|
||||
AZ::IO::Path absoluteFilePath = AZ::IO::PathView(GetTempDir()).RootPath();
|
||||
absoluteFilePath /= relFilePath;
|
||||
absoluteFilePath = absoluteFilePath.LexicallyNormal();
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace AssetBundler
|
||||
TEST_F(MockUtilsTest, TestFilePath_RelativePath_Valid)
|
||||
{
|
||||
AZ::IO::Path relFilePath = "Foo\\foo.xml";
|
||||
AZ::IO::Path absoluteFilePath = (AZ::IO::Path(GetEngineRoot()) / relFilePath).LexicallyNormal();
|
||||
AZ::IO::Path absoluteFilePath = (AZ::IO::Path(GetTempDir()) / relFilePath).LexicallyNormal();
|
||||
FilePath filePath(relFilePath.Native());
|
||||
EXPECT_EQ(AZ::IO::PathView{ filePath.AbsolutePath() }, absoluteFilePath);
|
||||
}
|
||||
@@ -107,8 +107,8 @@ namespace AssetBundler
|
||||
AZ::IO::Path relFilePath = "Foo\\Foo.xml";
|
||||
AZ::IO::Path wrongCaseRelFilePath = "Foo\\foo.xml";
|
||||
|
||||
AZ::IO::Path correctAbsoluteFilePath = (AZ::IO::Path(GetEngineRoot()) / relFilePath).LexicallyNormal();
|
||||
AZ::IO::Path wrongCaseAbsoluteFilePath = (AZ::IO::Path(GetEngineRoot()) / wrongCaseRelFilePath).LexicallyNormal();
|
||||
AZ::IO::Path correctAbsoluteFilePath = (AZ::IO::Path(GetTempDir()) / relFilePath).LexicallyNormal();
|
||||
AZ::IO::Path wrongCaseAbsoluteFilePath = (AZ::IO::Path(GetTempDir()) / wrongCaseRelFilePath).LexicallyNormal();
|
||||
|
||||
AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle;
|
||||
AZ::IO::FileIOBase::GetInstance()->Open(correctAbsoluteFilePath.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath, fileHandle);
|
||||
@@ -121,7 +121,7 @@ namespace AssetBundler
|
||||
TEST_F(MockUtilsTest, TestFilePath_NoFileExists_NoError_valid)
|
||||
{
|
||||
AZ::IO::Path relFilePath = "Foo\\Foo.xml";
|
||||
AZ::IO::Path absoluteFilePath = (AZ::IO::Path(GetEngineRoot()) / relFilePath).LexicallyNormal();
|
||||
AZ::IO::Path absoluteFilePath = (AZ::IO::Path(GetTempDir()) / relFilePath).LexicallyNormal();
|
||||
|
||||
FilePath filePath(absoluteFilePath.Native(), true, false);
|
||||
EXPECT_TRUE(filePath.IsValid());
|
||||
@@ -132,8 +132,8 @@ namespace AssetBundler
|
||||
{
|
||||
AZStd::string relFilePath = "Foo\\Foo.xml";
|
||||
AZStd::string wrongCaseRelFilePath = "Foo\\foo.xml";
|
||||
AZ::IO::Path correctAbsoluteFilePath = (AZ::IO::Path(GetEngineRoot()) / relFilePath).LexicallyNormal();
|
||||
AZ::IO::Path wrongCaseAbsoluteFilePath = (AZ::IO::Path(GetEngineRoot()) / wrongCaseRelFilePath).LexicallyNormal();
|
||||
AZ::IO::Path correctAbsoluteFilePath = (AZ::IO::Path(GetTempDir()) / relFilePath).LexicallyNormal();
|
||||
AZ::IO::Path wrongCaseAbsoluteFilePath = (AZ::IO::Path(GetTempDir()) / wrongCaseRelFilePath).LexicallyNormal();
|
||||
|
||||
AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle;
|
||||
AZ::IO::FileIOBase::GetInstance()->Open(correctAbsoluteFilePath.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath, fileHandle);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <AzCore/Settings/SettingsRegistryImpl.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/UserSettings/UserSettingsComponent.h>
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
|
||||
#include <source/utils/utils.h>
|
||||
#include <source/utils/applicationManager.h>
|
||||
@@ -84,10 +85,9 @@ namespace AssetBundler
|
||||
// in the unit tests.
|
||||
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
|
||||
|
||||
const char* engineRoot = nullptr;
|
||||
AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot);
|
||||
ASSERT_TRUE(engineRoot) << "Unable to locate engine root.\n";
|
||||
AzFramework::StringFunc::Path::Join(engineRoot, RelativeTestFolder, m_data->m_testEngineRoot);
|
||||
AZ::IO::FixedMaxPath engineRoot = AZ::Utils::GetEnginePath();
|
||||
ASSERT_TRUE(!engineRoot.empty()) << "Unable to locate engine root.\n";
|
||||
m_data->m_testEngineRoot = (engineRoot / RelativeTestFolder).String();
|
||||
|
||||
m_data->m_localFileIO = aznew AZ::IO::LocalFileIO();
|
||||
m_data->m_priorFileIO = AZ::IO::FileIOBase::GetInstance();
|
||||
@@ -150,7 +150,8 @@ namespace AssetBundler
|
||||
|
||||
EXPECT_EQ(0, gemsNameMap.size());
|
||||
|
||||
AzFramework::PlatformFlags platformFlags = GetEnabledPlatformFlags(m_data->m_testEngineRoot.c_str(), m_data->m_testEngineRoot.c_str(), DummyProjectName);
|
||||
const auto testProjectPath = AZ::IO::Path(m_data->m_testEngineRoot) / DummyProjectName;
|
||||
AzFramework::PlatformFlags platformFlags = GetEnabledPlatformFlags(m_data->m_testEngineRoot, testProjectPath.Native());
|
||||
AzFramework::PlatformFlags hostPlatformFlag = AzFramework::PlatformHelper::GetPlatformFlag(AzToolsFramework::AssetSystem::GetHostAssetPlatform());
|
||||
AzFramework::PlatformFlags expectedFlags = AzFramework::PlatformFlags::Platform_ANDROID | AzFramework::PlatformFlags::Platform_IOS | AzFramework::PlatformFlags::Platform_PROVO | hostPlatformFlag;
|
||||
ASSERT_EQ(platformFlags, expectedFlags);
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <xxhash/xxhash.h>
|
||||
|
||||
namespace AssetBuilderSDK
|
||||
{
|
||||
const char* const ErrorWindow = "Error"; //Use this window name to log error messages.
|
||||
@@ -690,7 +692,6 @@ namespace AssetBuilderSDK
|
||||
static const char* textureExtensions = ".dds";
|
||||
static const char* staticMeshExtensions = ".cgf";
|
||||
static const char* skinnedMeshExtensions = ".skin";
|
||||
static const char* materialExtensions = ".mtl";
|
||||
|
||||
// MIPS
|
||||
static const int c_MaxMipsCount = 11; // 11 is for 8k textures non-compressed. When not compressed it is using one file per mip.
|
||||
@@ -699,7 +700,6 @@ namespace AssetBuilderSDK
|
||||
|
||||
// XML files may contain generic data (avoid this in new builders - use a custom extension!)
|
||||
static const char* xmlExtensions = ".xml";
|
||||
static const char* geomCacheExtensions = ".cax";
|
||||
static const char* skeletonExtensions = ".chr";
|
||||
|
||||
static AZ::Data::AssetType unknownAssetType = AZ::Data::AssetType::CreateNull();
|
||||
@@ -710,7 +710,6 @@ namespace AssetBuilderSDK
|
||||
static AZ::Data::AssetType textureMipsAssetType("{3918728C-D3CA-4D9E-813E-A5ED20C6821E}");
|
||||
static AZ::Data::AssetType skinnedMeshLodsAssetType("{58E5824F-C27B-46FD-AD48-865BA41B7A51}");
|
||||
static AZ::Data::AssetType staticMeshLodsAssetType("{9AAE4926-CB6A-4C60-9948-A1A22F51DB23}");
|
||||
static AZ::Data::AssetType geomCacheAssetType("{EBC96071-E960-41B6-B3E3-328F515AE5DA}");
|
||||
static AZ::Data::AssetType skeletonAssetType("{60161B46-21F0-4396-A4F0-F2CCF0664CDE}");
|
||||
static AZ::Data::AssetType entityIconAssetType("{3436C30E-E2C5-4C3B-A7B9-66C94A28701B}");
|
||||
|
||||
@@ -807,11 +806,6 @@ namespace AssetBuilderSDK
|
||||
return textureAssetType;
|
||||
}
|
||||
|
||||
if (AzFramework::StringFunc::Find(materialExtensions, extension.c_str()) != AZStd::string::npos)
|
||||
{
|
||||
return materialAssetType;
|
||||
}
|
||||
|
||||
if (AzFramework::StringFunc::Find(staticMeshExtensions, extension.c_str()) != AZStd::string::npos)
|
||||
{
|
||||
return meshAssetType;
|
||||
@@ -822,11 +816,6 @@ namespace AssetBuilderSDK
|
||||
return skinnedMeshAssetType;
|
||||
}
|
||||
|
||||
if (AzFramework::StringFunc::Find(geomCacheExtensions, extension.c_str()) != AZStd::string::npos)
|
||||
{
|
||||
return geomCacheAssetType;
|
||||
}
|
||||
|
||||
if (AzFramework::StringFunc::Find(skeletonExtensions, extension.c_str()) != AZStd::string::npos)
|
||||
{
|
||||
return skeletonAssetType;
|
||||
@@ -1612,4 +1601,70 @@ namespace AssetBuilderSDK
|
||||
{
|
||||
return m_errorsOccurred;
|
||||
}
|
||||
|
||||
AZ::u64 GetHashFromIOStream(AZ::IO::GenericStream& readStream, AZ::IO::SizeType* bytesReadOut, int hashMsDelay)
|
||||
{
|
||||
constexpr AZ::u64 HashBufferSize = 1024 * 64;
|
||||
char buffer[HashBufferSize];
|
||||
|
||||
if(readStream.IsOpen() && readStream.CanRead())
|
||||
{
|
||||
AZ::IO::SizeType bytesRead;
|
||||
|
||||
auto* state = XXH64_createState();
|
||||
|
||||
if(state == nullptr)
|
||||
{
|
||||
AZ_Assert(false, "Failed to create hash state");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (XXH64_reset(state, 0) == XXH_ERROR)
|
||||
{
|
||||
AZ_Assert(false, "Failed to reset hash state");
|
||||
return 0;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
// In edge cases where another process is writing to this file while this hashing is occuring and that file wasn't locked,
|
||||
// the following read check can fail because it performs an end of file check, and asserts and shuts down if the read size
|
||||
// was smaller than the buffer and the read is not at the end of the file. The logic used to check end of file internal to read
|
||||
// will be out of date in the edge cases where another process is actively writing to this file while this hash is running.
|
||||
// The stream's length ends up more accurate in this case, preventing this assert and shut down.
|
||||
// One area this occurs is the navigation mesh file (mnmnavmission0.bai) that's temporarily created when exporting a level,
|
||||
// the navigation system can still be writing to this file when hashing begins, causing the EoF marker to change.
|
||||
AZ::IO::SizeType remainingToRead = AZStd::min(readStream.GetLength() - readStream.GetCurPos(), aznumeric_cast<AZ::IO::SizeType>(AZ_ARRAY_SIZE(buffer)));
|
||||
bytesRead = readStream.Read(remainingToRead, buffer);
|
||||
|
||||
if(bytesReadOut)
|
||||
{
|
||||
*bytesReadOut += bytesRead;
|
||||
}
|
||||
|
||||
XXH64_update(state, buffer, bytesRead);
|
||||
|
||||
// Used by unit tests to force the race condition mentioned above, to verify the crash fix.
|
||||
if(hashMsDelay > 0)
|
||||
{
|
||||
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(hashMsDelay));
|
||||
}
|
||||
|
||||
} while (bytesRead > 0);
|
||||
|
||||
auto hash = XXH64_digest(state);
|
||||
|
||||
XXH64_freeState(state);
|
||||
|
||||
return hash;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
AZ::u64 GetFileHash(const char* filePath, AZ::IO::SizeType* bytesReadOut, int hashMsDelay)
|
||||
{
|
||||
constexpr bool ErrorOnReadFailure = true;
|
||||
AZ::IO::FileIOStream readStream(filePath, AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary, ErrorOnReadFailure);
|
||||
return GetHashFromIOStream(readStream, bytesReadOut, hashMsDelay);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -911,6 +911,19 @@ namespace AssetBuilderSDK
|
||||
//! There can be multiple builders running at once, so we need to filter out ones coming from other builders
|
||||
AZStd::thread_id m_jobThreadId;
|
||||
};
|
||||
|
||||
//! Get hash for a whole file
|
||||
//! @filePath the path for the file
|
||||
//! @bytesReadOut output the read file size in bytes
|
||||
//! @hashMsDelay [Do not use except for unit test] add a delay in ms for between each block reading.
|
||||
AZ::u64 GetFileHash(const char* filePath, AZ::IO::SizeType* bytesReadOut = nullptr, int hashMsDelay = 0);
|
||||
|
||||
//! Get hash for a generic IO stream
|
||||
//! @readStream the input readable stream
|
||||
//! @bytesReadOut output the read size in bytes
|
||||
//! @hashMsDelay [Do not use except for unit test] add a delay in ms for between each block reading.
|
||||
AZ::u64 GetHashFromIOStream(AZ::IO::GenericStream& readStream, AZ::IO::SizeType* bytesReadOut = nullptr, int hashMsDelay = 0);
|
||||
|
||||
} // namespace AssetBuilderSDK
|
||||
|
||||
namespace AZ
|
||||
|
||||
@@ -32,6 +32,7 @@ ly_add_target(
|
||||
PUBLIC
|
||||
AZ::AzFramework
|
||||
AZ::AzToolsFramework
|
||||
3rdParty::xxhash
|
||||
)
|
||||
ly_add_source_properties(
|
||||
SOURCES AssetBuilderSDK/AssetBuilderSDK.cpp
|
||||
|
||||
@@ -32,7 +32,8 @@ struct FolderRootWatch::PlatformImplementation
|
||||
{
|
||||
if (m_iNotifyHandle < 0)
|
||||
{
|
||||
m_iNotifyHandle = inotify_init();
|
||||
// The CLOEXEC flag prevents the inotify watchers from copying on fork/exec
|
||||
m_iNotifyHandle = inotify_init1(IN_CLOEXEC);
|
||||
}
|
||||
return (m_iNotifyHandle >= 0);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,9 @@ set(FILES
|
||||
native/AssetManager/SourceFileRelocator.h
|
||||
native/AssetManager/ControlRequestHandler.cpp
|
||||
native/AssetManager/ControlRequestHandler.h
|
||||
native/AssetManager/ExcludedFolderCache.cpp
|
||||
native/AssetManager/ExcludedFolderCache.h
|
||||
native/AssetManager/ExcludedFolderCacheInterface.h
|
||||
native/assetprocessor.h
|
||||
native/connection/connection.cpp
|
||||
native/connection/connection.h
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QDirIterator>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <native/AssetManager/ExcludedFolderCache.h>
|
||||
#include <utilities/assetUtils.h>
|
||||
#include <utilities/PlatformConfiguration.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
|
||||
namespace AssetProcessor
|
||||
{
|
||||
ExcludedFolderCache::ExcludedFolderCache(const PlatformConfiguration* platformConfig) : m_platformConfig(platformConfig)
|
||||
{
|
||||
AZ::Interface<ExcludedFolderCacheInterface>::Register(this);
|
||||
}
|
||||
|
||||
ExcludedFolderCache::~ExcludedFolderCache()
|
||||
{
|
||||
AZ::Interface<ExcludedFolderCacheInterface>::Unregister(this);
|
||||
}
|
||||
|
||||
const AZStd::unordered_set<AZStd::string>& ExcludedFolderCache::GetExcludedFolders()
|
||||
{
|
||||
if (!m_builtCache)
|
||||
{
|
||||
for (int i = 0; i < m_platformConfig->GetScanFolderCount(); ++i)
|
||||
{
|
||||
const auto& scanFolderInfo = m_platformConfig->GetScanFolderAt(i);
|
||||
QDir rooted(scanFolderInfo.ScanPath());
|
||||
QString absolutePath = rooted.absolutePath();
|
||||
AZStd::stack<QString> dirs;
|
||||
dirs.push(absolutePath);
|
||||
|
||||
while (!dirs.empty())
|
||||
{
|
||||
absolutePath = dirs.top();
|
||||
dirs.pop();
|
||||
|
||||
// Scan only folders, do not recurse so we have the chance to ignore a subfolder before going deeper
|
||||
QDirIterator dirIterator(absolutePath, QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot);
|
||||
|
||||
// Loop all the folders in this directory
|
||||
while (dirIterator.hasNext())
|
||||
{
|
||||
dirIterator.next();
|
||||
QString pathMatch = rooted.absoluteFilePath(dirIterator.filePath());
|
||||
|
||||
if (m_platformConfig->IsFileExcluded(pathMatch))
|
||||
{
|
||||
// Add the folder to the list and do not proceed any deeper
|
||||
m_excludedFolders.emplace(pathMatch.toUtf8().constData());
|
||||
}
|
||||
else if (scanFolderInfo.RecurseSubFolders())
|
||||
{
|
||||
// Folder is not excluded and recurse is enabled, add to the list of folders to check
|
||||
dirs.push(pathMatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the cache to the list as well
|
||||
AZStd::string projectCacheRootValue;
|
||||
AZ::SettingsRegistry::Get()->Get(projectCacheRootValue, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder);
|
||||
projectCacheRootValue = AssetUtilities::NormalizeFilePath(projectCacheRootValue.c_str()).toUtf8().constData();
|
||||
m_excludedFolders.emplace(projectCacheRootValue);
|
||||
|
||||
// Register to be notified about deletes so we can remove old ignored folders
|
||||
auto fileStateCache = AZ::Interface<IFileStateRequests>::Get();
|
||||
|
||||
if (fileStateCache)
|
||||
{
|
||||
m_handler = AZ::Event<FileStateInfo>::Handler([this](FileStateInfo fileInfo)
|
||||
{
|
||||
if (fileInfo.m_isDirectory)
|
||||
{
|
||||
AZStd::scoped_lock lock(m_pendingNewFolderMutex);
|
||||
|
||||
m_pendingDeletes.emplace(fileInfo.m_absolutePath.toUtf8().constData());
|
||||
}
|
||||
});
|
||||
|
||||
fileStateCache->RegisterForDeleteEvent(m_handler);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error("ExcludedFolderCache", false, "Failed to find IFileStateRequests interface");
|
||||
}
|
||||
|
||||
m_builtCache = true;
|
||||
}
|
||||
|
||||
// Incorporate any pending folders
|
||||
AZStd::unordered_set<AZStd::string> pendingAdds;
|
||||
AZStd::unordered_set<AZStd::string> pendingDeletes;
|
||||
|
||||
{
|
||||
AZStd::scoped_lock lock(m_pendingNewFolderMutex);
|
||||
pendingAdds.swap(m_pendingNewFolders);
|
||||
pendingDeletes.swap(m_pendingDeletes);
|
||||
}
|
||||
|
||||
if (!pendingAdds.empty())
|
||||
{
|
||||
m_excludedFolders.insert(pendingAdds.begin(), pendingAdds.end());
|
||||
}
|
||||
|
||||
if (!pendingDeletes.empty())
|
||||
{
|
||||
for (const auto& pendingDelete : pendingDeletes)
|
||||
{
|
||||
m_excludedFolders.erase(pendingDelete);
|
||||
}
|
||||
}
|
||||
|
||||
return m_excludedFolders;
|
||||
}
|
||||
|
||||
void ExcludedFolderCache::FileAdded(QString path)
|
||||
{
|
||||
QString relativePath, scanFolderPath;
|
||||
|
||||
if (!m_platformConfig->ConvertToRelativePath(path, relativePath, scanFolderPath))
|
||||
{
|
||||
AZ_Error("ExcludedFolderCache", false, "Failed to get relative path for newly added file %s", path.toUtf8().constData());
|
||||
return;
|
||||
}
|
||||
|
||||
AZ::IO::Path azPath(relativePath.toUtf8().constData());
|
||||
AZ::IO::Path absolutePath(scanFolderPath.toUtf8().constData());
|
||||
|
||||
for (const auto& pathPart : azPath)
|
||||
{
|
||||
absolutePath /= pathPart;
|
||||
|
||||
QString normalized = AssetUtilities::NormalizeFilePath(absolutePath.c_str());
|
||||
|
||||
if (m_platformConfig->IsFileExcluded(normalized))
|
||||
{
|
||||
// Add the folder to a pending list, since this callback runs on another thread
|
||||
AZStd::scoped_lock lock(m_pendingNewFolderMutex);
|
||||
|
||||
m_pendingNewFolders.emplace(normalized.toUtf8().constData());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AssetManager/ExcludedFolderCacheInterface.h>
|
||||
#include <AssetManager/FileStateCache.h>
|
||||
|
||||
namespace AssetProcessor
|
||||
{
|
||||
class PlatformConfiguration;
|
||||
|
||||
struct ExcludedFolderCache : ExcludedFolderCacheInterface
|
||||
{
|
||||
explicit ExcludedFolderCache(const PlatformConfiguration* platformConfig);
|
||||
~ExcludedFolderCache() override;
|
||||
|
||||
// Gets a set of absolute paths to folder which have been excluded according to the platform configuration rules
|
||||
// Note - not thread safe
|
||||
const AZStd::unordered_set<AZStd::string>& GetExcludedFolders() override;
|
||||
|
||||
void FileAdded(QString path) override;
|
||||
|
||||
private:
|
||||
bool m_builtCache = false;
|
||||
const PlatformConfiguration* m_platformConfig{};
|
||||
AZStd::unordered_set<AZStd::string> m_excludedFolders;
|
||||
|
||||
AZStd::recursive_mutex m_pendingNewFolderMutex;
|
||||
AZStd::unordered_set<AZStd::string> m_pendingNewFolders; // Newly ignored folders waiting to be added to m_excludedFolders
|
||||
AZStd::unordered_set<AZStd::string> m_pendingDeletes;
|
||||
AZ::Event<FileStateInfo>::Handler m_handler;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/std/containers/unordered_set.h>
|
||||
#include <QString>
|
||||
|
||||
namespace AssetProcessor
|
||||
{
|
||||
class PlatformConfiguration;
|
||||
|
||||
struct ExcludedFolderCacheInterface
|
||||
{
|
||||
AZ_RTTI(ExcludedFolderCacheInterface, "{3AC471B6-C9F8-49CF-9E9D-237BDF63328C}");
|
||||
AZ_DISABLE_COPY_MOVE(ExcludedFolderCacheInterface);
|
||||
|
||||
ExcludedFolderCacheInterface() = default;
|
||||
virtual ~ExcludedFolderCacheInterface() = default;
|
||||
|
||||
virtual const AZStd::unordered_set<AZStd::string>& GetExcludedFolders() = 0;
|
||||
virtual void FileAdded(QString path) = 0;
|
||||
};
|
||||
}
|
||||
@@ -63,6 +63,11 @@ namespace AssetProcessor
|
||||
return true;
|
||||
}
|
||||
|
||||
void FileStateCache::RegisterForDeleteEvent(AZ::Event<FileStateInfo>::Handler& handler)
|
||||
{
|
||||
handler.Connect(m_deleteEvent);
|
||||
}
|
||||
|
||||
void FileStateCache::AddInfoSet(QSet<AssetFileInfo> infoSet)
|
||||
{
|
||||
LockGuardType scopeLock(m_mapMutex);
|
||||
@@ -103,6 +108,8 @@ namespace AssetProcessor
|
||||
|
||||
if (itr != m_fileInfoMap.end())
|
||||
{
|
||||
m_deleteEvent.Signal(itr.value());
|
||||
|
||||
bool isDirectory = itr.value().m_isDirectory;
|
||||
QString parentPath = itr.value().m_absolutePath;
|
||||
m_fileInfoMap.erase(itr);
|
||||
@@ -205,6 +212,21 @@ namespace AssetProcessor
|
||||
return true;
|
||||
}
|
||||
|
||||
void FileStatePassthrough::RegisterForDeleteEvent(AZ::Event<FileStateInfo>::Handler& handler)
|
||||
{
|
||||
handler.Connect(m_deleteEvent);
|
||||
}
|
||||
|
||||
void FileStatePassthrough::SignalDeleteEvent(const QString& absolutePath) const
|
||||
{
|
||||
FileStateInfo info;
|
||||
|
||||
if (GetFileInfo(absolutePath, &info))
|
||||
{
|
||||
m_deleteEvent.Signal(info);
|
||||
}
|
||||
}
|
||||
|
||||
bool FileStateInfo::operator==(const FileStateInfo& rhs) const
|
||||
{
|
||||
return m_absolutePath == rhs.m_absolutePath
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <QSet>
|
||||
#include <QFileInfo>
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/EBus/Event.h>
|
||||
|
||||
namespace AssetProcessor
|
||||
{
|
||||
@@ -51,10 +52,11 @@ namespace AssetProcessor
|
||||
/// Convenience function to check if a file or directory exists.
|
||||
virtual bool Exists(const QString& absolutePath) const = 0;
|
||||
virtual bool GetHash(const QString& absolutePath, FileHash* foundHash) = 0;
|
||||
virtual void RegisterForDeleteEvent(AZ::Event<FileStateInfo>::Handler& handler) = 0;
|
||||
|
||||
AZ_DISABLE_COPY_MOVE(IFileStateRequests);
|
||||
};
|
||||
|
||||
|
||||
class FileStateBase
|
||||
: public IFileStateRequests
|
||||
{
|
||||
@@ -89,11 +91,11 @@ namespace AssetProcessor
|
||||
{
|
||||
public:
|
||||
|
||||
|
||||
// FileStateRequestBus implementation
|
||||
bool GetFileInfo(const QString& absolutePath, FileStateInfo* foundFileInfo) const override;
|
||||
bool Exists(const QString& absolutePath) const override;
|
||||
bool GetHash(const QString& absolutePath, FileHash* foundHash) override;
|
||||
void RegisterForDeleteEvent(AZ::Event<FileStateInfo>::Handler& handler) override;
|
||||
|
||||
void AddInfoSet(QSet<AssetFileInfo> infoSet) override;
|
||||
void AddFile(const QString& absolutePath) override;
|
||||
@@ -116,9 +118,11 @@ namespace AssetProcessor
|
||||
|
||||
mutable AZStd::recursive_mutex m_mapMutex;
|
||||
QHash<QString, FileStateInfo> m_fileInfoMap;
|
||||
|
||||
|
||||
QHash<QString, FileHash> m_fileHashMap;
|
||||
|
||||
AZ::Event<FileStateInfo> m_deleteEvent;
|
||||
|
||||
using LockGuardType = AZStd::lock_guard<decltype(m_mapMutex)>;
|
||||
};
|
||||
|
||||
@@ -131,5 +135,10 @@ namespace AssetProcessor
|
||||
bool GetFileInfo(const QString& absolutePath, FileStateInfo* foundFileInfo) const override;
|
||||
bool Exists(const QString& absolutePath) const override;
|
||||
bool GetHash(const QString& absolutePath, FileHash* foundHash) override;
|
||||
void RegisterForDeleteEvent(AZ::Event<FileStateInfo>::Handler& handler) override;
|
||||
|
||||
void SignalDeleteEvent(const QString& absolutePath) const;
|
||||
protected:
|
||||
AZ::Event<FileStateInfo> m_deleteEvent;
|
||||
};
|
||||
} // namespace AssetProcessor
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
|
||||
#include <AzToolsFramework/Debug/TraceContext.h>
|
||||
|
||||
|
||||
#include "native/AssetManager/assetProcessorManager.h"
|
||||
|
||||
#include <AzCore/std/sort.h>
|
||||
#include <AzToolsFramework/API/AssetDatabaseBus.h>
|
||||
|
||||
@@ -66,8 +66,10 @@ namespace AssetProcessor
|
||||
|
||||
m_sourceFileRelocator = AZStd::make_unique<SourceFileRelocator>(m_stateData, m_platformConfig);
|
||||
|
||||
PopulateJobStateCache();
|
||||
m_excludedFolderCache = AZStd::make_unique<ExcludedFolderCache>(m_platformConfig);
|
||||
|
||||
PopulateJobStateCache();
|
||||
|
||||
AssetProcessor::ProcessingJobInfoBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
@@ -3573,6 +3575,8 @@ namespace AssetProcessor
|
||||
QString knownPathBeforeWildcard = encodedFileData.left(slashBeforeWildcardIndex + 1); // include the slash
|
||||
QString relativeSearch = encodedFileData.mid(slashBeforeWildcardIndex + 1); // skip the slash
|
||||
|
||||
const auto& excludedFolders = m_excludedFolderCache->GetExcludedFolders();
|
||||
|
||||
// Absolute path, just check the 1 scan folder
|
||||
if (AZ::IO::PathView(encodedFileData.toUtf8().constData()).IsAbsolute())
|
||||
{
|
||||
@@ -3592,7 +3596,8 @@ namespace AssetProcessor
|
||||
QString scanFolderAndKnownSubPath = rooted.absoluteFilePath(knownPathBeforeWildcard);
|
||||
|
||||
resolvedDependencyList.append(m_platformConfig->FindWildcardMatches(
|
||||
scanFolderAndKnownSubPath, relativeSearch, false, scanFolderInfo->RecurseSubFolders()));
|
||||
scanFolderAndKnownSubPath, relativeSearch,
|
||||
excludedFolders, false, scanFolderInfo->RecurseSubFolders()));
|
||||
}
|
||||
}
|
||||
else // Relative path, check every scan folder
|
||||
@@ -3610,7 +3615,21 @@ namespace AssetProcessor
|
||||
QString absolutePath = rooted.absoluteFilePath(knownPathBeforeWildcard);
|
||||
|
||||
resolvedDependencyList.append(m_platformConfig->FindWildcardMatches(
|
||||
absolutePath, relativeSearch, false, scanFolderInfo->RecurseSubFolders()));
|
||||
absolutePath, relativeSearch,
|
||||
excludedFolders, false, scanFolderInfo->RecurseSubFolders()));
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out any excluded files
|
||||
for (auto itr = resolvedDependencyList.begin(); itr != resolvedDependencyList.end();)
|
||||
{
|
||||
if (m_platformConfig->IsFileExcluded(*itr))
|
||||
{
|
||||
itr = resolvedDependencyList.erase(itr);
|
||||
}
|
||||
else
|
||||
{
|
||||
++itr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
#include "AssetRequestHandler.h"
|
||||
#include "native/utilities/JobDiagnosticTracker.h"
|
||||
#include "SourceFileRelocator.h"
|
||||
|
||||
#include <AssetManager/ExcludedFolderCache.h>
|
||||
#endif
|
||||
|
||||
class FileWatcher;
|
||||
@@ -341,7 +343,8 @@ namespace AssetProcessor
|
||||
void CleanEmptyFolder(QString folder, QString root);
|
||||
|
||||
void ProcessBuilders(QString normalizedPath, QString relativePathToFile, const ScanFolderInfo* scanFolder, const AssetProcessor::BuilderInfoList& builderInfoList);
|
||||
|
||||
AZStd::vector<AZStd::string> GetExcludedFolders();
|
||||
|
||||
struct SourceInfo
|
||||
{
|
||||
QString m_watchFolder;
|
||||
@@ -552,6 +555,8 @@ namespace AssetProcessor
|
||||
// when true, a flag will be sent to builders process job indicating debug output/mode should be used
|
||||
bool m_builderDebugFlag = false;
|
||||
|
||||
AZStd::unique_ptr<ExcludedFolderCache> m_excludedFolderCache{};
|
||||
|
||||
protected Q_SLOTS:
|
||||
void FinishAnalysis(AZStd::string fileToCheck);
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace AssetProcessor
|
||||
{
|
||||
protected:
|
||||
AZStd::unique_ptr<UnitTestUtils::AssertAbsorber> m_errorAbsorber{};
|
||||
FileStatePassthrough m_fileStateCache;
|
||||
AZStd::unique_ptr<FileStatePassthrough> m_fileStateCache{};
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
@@ -40,9 +40,10 @@ namespace AssetProcessor
|
||||
m_ownsSysAllocator = true;
|
||||
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
|
||||
}
|
||||
m_errorAbsorber = AZStd::make_unique<UnitTestUtils::AssertAbsorber>();
|
||||
|
||||
m_errorAbsorber = AZStd::make_unique<UnitTestUtils::AssertAbsorber>();
|
||||
m_application = AZStd::make_unique<AzFramework::Application>();
|
||||
m_fileStateCache = AZStd::make_unique<FileStatePassthrough>();
|
||||
|
||||
// Inject the AutomatedTesting project as a project path into test fixture
|
||||
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
|
||||
@@ -60,7 +61,8 @@ namespace AssetProcessor
|
||||
void TearDown() override
|
||||
{
|
||||
AssetUtilities::ResetAssetRoot();
|
||||
|
||||
|
||||
m_fileStateCache.reset();
|
||||
m_application.reset();
|
||||
m_errorAbsorber.reset();
|
||||
|
||||
|
||||
@@ -5364,13 +5364,29 @@ AZStd::vector<AZStd::string> WildcardSourceDependencyTest::FileAddedTest(const Q
|
||||
void WildcardSourceDependencyTest::SetUp()
|
||||
{
|
||||
AssetProcessorManagerTest::SetUp();
|
||||
|
||||
|
||||
QDir tempPath(m_tempDir.path());
|
||||
|
||||
// Add a non-recursive scan folder. Only files directly inside of this folder should be picked up, subfolders are ignored
|
||||
m_config->AddScanFolder(ScanFolderInfo(tempPath.filePath("no_recurse"), "no_recurse",
|
||||
"no_recurse", false, false, m_config->GetEnabledPlatforms(), 1));
|
||||
|
||||
{
|
||||
ExcludeAssetRecognizer excludeFolder;
|
||||
excludeFolder.m_name = "Exclude ignored Folder";
|
||||
excludeFolder.m_patternMatcher =
|
||||
AssetBuilderSDK::FilePatternMatcher(R"REGEX(^(.*\/)?ignored(\/.*)?$)REGEX", AssetBuilderSDK::AssetBuilderPattern::Regex);
|
||||
m_config->AddExcludeRecognizer(excludeFolder);
|
||||
}
|
||||
|
||||
{
|
||||
ExcludeAssetRecognizer excludeFile;
|
||||
excludeFile.m_name = "Exclude z.foo Files";
|
||||
excludeFile.m_patternMatcher =
|
||||
AssetBuilderSDK::FilePatternMatcher(R"REGEX(^(.*\/)?z\.foo$)REGEX", AssetBuilderSDK::AssetBuilderPattern::Regex);
|
||||
m_config->AddExcludeRecognizer(excludeFile);
|
||||
}
|
||||
|
||||
UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("subfolder1/1a.foo"));
|
||||
UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("subfolder1/1b.foo"));
|
||||
UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("subfolder2/redirected/a.foo"));
|
||||
@@ -5384,6 +5400,19 @@ void WildcardSourceDependencyTest::SetUp()
|
||||
// Add a file in the non-recursive scanfolder. Since its not directly in the scan folder, it should always be ignored
|
||||
UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("no_recurse/one/two/three/f.foo"));
|
||||
|
||||
// Add a file to an ignored folder
|
||||
UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("subfolder2/redirected/folder/ignored/g.foo"));
|
||||
|
||||
// Add an ignored file
|
||||
UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("subfolder2/redirected/folder/one/z.foo"));
|
||||
|
||||
// Add a file in the cache
|
||||
AZStd::string projectCacheRootValue;
|
||||
AZ::SettingsRegistry::Get()->Get(projectCacheRootValue, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder);
|
||||
projectCacheRootValue = AssetUtilities::NormalizeFilePath(projectCacheRootValue.c_str()).toUtf8().constData();
|
||||
auto path = AZ::IO::Path(projectCacheRootValue) / "cache.foo";
|
||||
UnitTestUtils::CreateDummyFile(path.c_str());
|
||||
|
||||
AzToolsFramework::AssetDatabase::SourceFileDependencyEntryContainer dependencies;
|
||||
|
||||
// Relative path wildcard dependency
|
||||
@@ -5518,6 +5547,102 @@ TEST_F(WildcardSourceDependencyTest, Absolute_NoWildcard)
|
||||
ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre());
|
||||
}
|
||||
|
||||
TEST_F(WildcardSourceDependencyTest, Relative_IgnoredFolder)
|
||||
{
|
||||
AZStd::vector<AZStd::string> resolvedPaths;
|
||||
|
||||
ASSERT_TRUE(Test("*g.foo", resolvedPaths));
|
||||
ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre());
|
||||
}
|
||||
|
||||
TEST_F(WildcardSourceDependencyTest, Absolute_IgnoredFolder)
|
||||
{
|
||||
AZStd::vector<AZStd::string> resolvedPaths;
|
||||
QDir tempPath(m_tempDir.path());
|
||||
|
||||
ASSERT_TRUE(Test(tempPath.absoluteFilePath("*g.foo").toUtf8().constData(), resolvedPaths));
|
||||
ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre());
|
||||
}
|
||||
|
||||
TEST_F(WildcardSourceDependencyTest, Relative_IgnoredFile)
|
||||
{
|
||||
AZStd::vector<AZStd::string> resolvedPaths;
|
||||
|
||||
ASSERT_TRUE(Test("*z.foo", resolvedPaths));
|
||||
ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre());
|
||||
}
|
||||
|
||||
TEST_F(WildcardSourceDependencyTest, Absolute_IgnoredFile)
|
||||
{
|
||||
AZStd::vector<AZStd::string> resolvedPaths;
|
||||
QDir tempPath(m_tempDir.path());
|
||||
|
||||
ASSERT_TRUE(Test(tempPath.absoluteFilePath("*z.foo").toUtf8().constData(), resolvedPaths));
|
||||
ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre());
|
||||
}
|
||||
|
||||
TEST_F(WildcardSourceDependencyTest, Relative_CacheFolder)
|
||||
{
|
||||
AZStd::vector<AZStd::string> resolvedPaths;
|
||||
QDir tempPath(m_tempDir.path());
|
||||
|
||||
ASSERT_TRUE(Test("*cache.foo", resolvedPaths));
|
||||
ASSERT_THAT(resolvedPaths, ::testing::UnorderedElementsAre());
|
||||
}
|
||||
|
||||
TEST_F(WildcardSourceDependencyTest, FilesAddedAfterInitialCache)
|
||||
{
|
||||
AZStd::vector<AZStd::string> resolvedPaths;
|
||||
QDir tempPath(m_tempDir.path());
|
||||
|
||||
auto excludedFolderCacheInterface = AZ::Interface<ExcludedFolderCacheInterface>::Get();
|
||||
|
||||
ASSERT_TRUE(excludedFolderCacheInterface);
|
||||
|
||||
{
|
||||
const auto& excludedFolders = excludedFolderCacheInterface->GetExcludedFolders();
|
||||
|
||||
ASSERT_EQ(excludedFolders.size(), 2);
|
||||
}
|
||||
|
||||
// Add a file to a new ignored folder
|
||||
QString newFilePath = tempPath.absoluteFilePath("subfolder2/redirected/folder/two/ignored/three/new.foo");
|
||||
UnitTestUtils::CreateDummyFile(newFilePath);
|
||||
|
||||
excludedFolderCacheInterface->FileAdded(newFilePath);
|
||||
|
||||
const auto& excludedFolders = excludedFolderCacheInterface->GetExcludedFolders();
|
||||
|
||||
ASSERT_EQ(excludedFolders.size(), 3);
|
||||
ASSERT_THAT(excludedFolders, ::testing::Contains(AZStd::string(tempPath.absoluteFilePath("subfolder2/redirected/folder/two/ignored").toUtf8().constData())));
|
||||
}
|
||||
|
||||
TEST_F(WildcardSourceDependencyTest, FilesRemovedAfterInitialCache)
|
||||
{
|
||||
AZStd::vector<AZStd::string> resolvedPaths;
|
||||
QDir tempPath(m_tempDir.path());
|
||||
|
||||
// Add a file to a new ignored folder
|
||||
QString newFilePath = tempPath.absoluteFilePath("subfolder2/redirected/folder/two/ignored/three/new.foo");
|
||||
UnitTestUtils::CreateDummyFile(newFilePath);
|
||||
|
||||
auto excludedFolderCacheInterface = AZ::Interface<ExcludedFolderCacheInterface>::Get();
|
||||
|
||||
ASSERT_TRUE(excludedFolderCacheInterface);
|
||||
|
||||
{
|
||||
const auto& excludedFolders = excludedFolderCacheInterface->GetExcludedFolders();
|
||||
|
||||
ASSERT_EQ(excludedFolders.size(), 3);
|
||||
}
|
||||
|
||||
m_fileStateCache->SignalDeleteEvent(tempPath.absoluteFilePath("subfolder2/redirected/folder/two/ignored"));
|
||||
|
||||
const auto& excludedFolders = excludedFolderCacheInterface->GetExcludedFolders();
|
||||
|
||||
ASSERT_EQ(excludedFolders.size(), 2);
|
||||
}
|
||||
|
||||
TEST_F(WildcardSourceDependencyTest, NewFile_MatchesSavedRelativeDependency)
|
||||
{
|
||||
QDir tempPath(m_tempDir.path());
|
||||
|
||||
+24
-12
@@ -52,11 +52,12 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_BadPlatform)
|
||||
using namespace AssetProcessor;
|
||||
|
||||
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
|
||||
const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / EmptyDummyProjectName;
|
||||
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_broken_badplatform");
|
||||
ASSERT_TRUE(configRoot);
|
||||
UnitTestPlatformConfiguration config;
|
||||
m_absorber.Clear();
|
||||
ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false));
|
||||
ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false));
|
||||
ASSERT_GT(m_absorber.m_numErrorsAbsorbed, 0);
|
||||
}
|
||||
|
||||
@@ -67,11 +68,12 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_NoPlatform)
|
||||
using namespace AssetProcessor;
|
||||
|
||||
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
|
||||
const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / EmptyDummyProjectName;
|
||||
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_broken_noplatform");
|
||||
ASSERT_TRUE(configRoot);
|
||||
UnitTestPlatformConfiguration config;
|
||||
m_absorber.Clear();
|
||||
ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false));
|
||||
ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false));
|
||||
ASSERT_GT(m_absorber.m_numErrorsAbsorbed, 0);
|
||||
}
|
||||
|
||||
@@ -81,11 +83,12 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_NoScanFolders)
|
||||
using namespace AssetProcessor;
|
||||
|
||||
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
|
||||
const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / EmptyDummyProjectName;
|
||||
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_broken_noscans");
|
||||
ASSERT_TRUE(configRoot);
|
||||
UnitTestPlatformConfiguration config;
|
||||
m_absorber.Clear();
|
||||
ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false));
|
||||
ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false));
|
||||
ASSERT_GT(m_absorber.m_numErrorsAbsorbed, 0);
|
||||
}
|
||||
|
||||
@@ -95,11 +98,12 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_BrokenRecognizers)
|
||||
using namespace AssetProcessor;
|
||||
|
||||
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
|
||||
const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / EmptyDummyProjectName;
|
||||
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_broken_recognizers");
|
||||
ASSERT_TRUE(configRoot);
|
||||
UnitTestPlatformConfiguration config;
|
||||
m_absorber.Clear();
|
||||
ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false));
|
||||
ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false));
|
||||
ASSERT_GT(m_absorber.m_numErrorsAbsorbed, 0);
|
||||
}
|
||||
|
||||
@@ -109,11 +113,12 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_Regular_Platforms)
|
||||
using namespace AssetProcessor;
|
||||
|
||||
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
|
||||
const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / EmptyDummyProjectName;
|
||||
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_regular");
|
||||
ASSERT_TRUE(configRoot);
|
||||
UnitTestPlatformConfiguration config;
|
||||
m_absorber.Clear();
|
||||
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false));
|
||||
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false));
|
||||
ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0);
|
||||
|
||||
// verify the data.
|
||||
@@ -322,12 +327,13 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_RegularScanfolder)
|
||||
using namespace AssetProcessor;
|
||||
|
||||
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
|
||||
const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / EmptyDummyProjectName;
|
||||
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_regular");
|
||||
ASSERT_TRUE(configRoot);
|
||||
UnitTestPlatformConfiguration config;
|
||||
m_absorber.Clear();
|
||||
AssetUtilities::ComputeProjectName(EmptyDummyProjectName, true);
|
||||
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false));
|
||||
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false));
|
||||
ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0);
|
||||
|
||||
ASSERT_EQ(config.GetScanFolderCount(), 3); // the two, and then the one that has the same data as prior but different identifier.
|
||||
@@ -356,11 +362,12 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_RegularScanfolderP
|
||||
using namespace AssetProcessor;
|
||||
|
||||
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
|
||||
const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / EmptyDummyProjectName;
|
||||
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_regular_platform_scanfolder");
|
||||
ASSERT_TRUE(configRoot);
|
||||
UnitTestPlatformConfiguration config;
|
||||
m_absorber.Clear();
|
||||
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false));
|
||||
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false));
|
||||
ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0);
|
||||
|
||||
ASSERT_EQ(config.GetScanFolderCount(), 5);
|
||||
@@ -402,13 +409,14 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_RegularExcludes)
|
||||
using namespace AssetProcessor;
|
||||
|
||||
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
|
||||
const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / EmptyDummyProjectName;
|
||||
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_regular");
|
||||
ASSERT_TRUE(configRoot);
|
||||
UnitTestPlatformConfiguration config;
|
||||
|
||||
config.AddScanFolder(ScanFolderInfo("blahblah", "Blah ScanFolder", "sf2", true, true), true);
|
||||
m_absorber.Clear();
|
||||
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false));
|
||||
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false));
|
||||
ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0);
|
||||
|
||||
ASSERT_TRUE(config.IsFileExcluded("blahblah/$tmp_01.test"));
|
||||
@@ -429,11 +437,12 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_Recognizers)
|
||||
#endif
|
||||
|
||||
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
|
||||
const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / EmptyDummyProjectName;
|
||||
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_regular");
|
||||
ASSERT_TRUE(configRoot);
|
||||
UnitTestPlatformConfiguration config;
|
||||
m_absorber.Clear();
|
||||
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false));
|
||||
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false));
|
||||
ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0);
|
||||
|
||||
const AssetProcessor::RecognizerContainer& recogs = config.GetAssetRecognizerContainer();
|
||||
@@ -520,12 +529,13 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_Overrides)
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
using namespace AssetProcessor;
|
||||
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
|
||||
const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / DummyProjectName;
|
||||
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_regular");
|
||||
ASSERT_TRUE(configRoot);
|
||||
UnitTestPlatformConfiguration config;
|
||||
m_absorber.Clear();
|
||||
|
||||
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), DummyProjectName, false, false));
|
||||
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false));
|
||||
ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0);
|
||||
|
||||
const AssetProcessor::RecognizerContainer& recogs = config.GetAssetRecognizerContainer();
|
||||
@@ -627,11 +637,12 @@ TEST_F(PlatformConfigurationUnitTests, ReadCheckServer_FromConfig_Valid)
|
||||
using namespace AssetProcessor;
|
||||
|
||||
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
|
||||
const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / EmptyDummyProjectName;
|
||||
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_regular");
|
||||
ASSERT_TRUE(configRoot);
|
||||
UnitTestPlatformConfiguration config;
|
||||
m_absorber.Clear();
|
||||
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false));
|
||||
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false));
|
||||
ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0);
|
||||
|
||||
const AssetProcessor::RecognizerContainer& recogs = config.GetAssetRecognizerContainer();
|
||||
@@ -676,11 +687,12 @@ TEST_F(PlatformConfigurationUnitTests, Test_MetaFileTypes_AssetImporterExtension
|
||||
using namespace AssetProcessor;
|
||||
|
||||
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
|
||||
const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / EmptyDummyProjectName;
|
||||
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_metadata");
|
||||
ASSERT_TRUE(configRoot);
|
||||
UnitTestPlatformConfiguration config;
|
||||
m_absorber.Clear();
|
||||
ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false));
|
||||
ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false));
|
||||
ASSERT_GT(m_absorber.m_numErrorsAbsorbed, 0);
|
||||
ASSERT_TRUE(config.MetaDataFileTypesCount() == 2);
|
||||
|
||||
|
||||
@@ -454,6 +454,8 @@ void ApplicationManagerBase::InitFileMonitor()
|
||||
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileModified, [this](QString path) { m_fileStateCache->UpdateFile(path); });
|
||||
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileRemoved, [this](QString path) { m_fileStateCache->RemoveFile(path); });
|
||||
|
||||
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileAdded, [](QString path) { AZ::Interface<AssetProcessor::ExcludedFolderCacheInterface>::Get()->FileAdded(path); });
|
||||
|
||||
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileAdded,
|
||||
m_fileProcessor.get(), &AssetProcessor::FileProcessor::AssessAddedFile);
|
||||
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileRemoved,
|
||||
|
||||
@@ -749,7 +749,7 @@ namespace AssetProcessor
|
||||
}
|
||||
|
||||
AZStd::vector<AZ::IO::Path> configFiles = AzToolsFramework::AssetUtils::GetConfigFiles(absoluteSystemRoot.toUtf8().constData(),
|
||||
absoluteAssetRoot.toUtf8().constData(), projectPath.toUtf8().constData(),
|
||||
projectPath.toUtf8().constData(),
|
||||
addPlatformConfigs, addGemsConfigs && !noGemScanFolders, settingsRegistry);
|
||||
|
||||
// First Merge all Engine, Gem and Project specific AssetProcessor*Config.setreg/.inifiles
|
||||
@@ -1285,6 +1285,13 @@ namespace AssetProcessor
|
||||
return m_scanFolders[index];
|
||||
}
|
||||
|
||||
const AssetProcessor::ScanFolderInfo& PlatformConfiguration::GetScanFolderAt(int index) const
|
||||
{
|
||||
Q_ASSERT(index >= 0);
|
||||
Q_ASSERT(index < m_scanFolders.size());
|
||||
return m_scanFolders[index];
|
||||
}
|
||||
|
||||
void PlatformConfiguration::AddScanFolder(const AssetProcessor::ScanFolderInfo& source, bool isUnitTesting)
|
||||
{
|
||||
if (isUnitTesting)
|
||||
@@ -1436,7 +1443,10 @@ namespace AssetProcessor
|
||||
}
|
||||
|
||||
QStringList PlatformConfiguration::FindWildcardMatches(
|
||||
const QString& sourceFolder, QString relativeName, bool includeFolders, bool recursiveSearch) const
|
||||
const QString& sourceFolder,
|
||||
QString relativeName,
|
||||
bool includeFolders,
|
||||
bool recursiveSearch) const
|
||||
{
|
||||
if (relativeName.isEmpty())
|
||||
{
|
||||
@@ -1469,6 +1479,67 @@ namespace AssetProcessor
|
||||
return returnList;
|
||||
}
|
||||
|
||||
QStringList PlatformConfiguration::FindWildcardMatches(
|
||||
const QString& sourceFolder,
|
||||
QString relativeName,
|
||||
const AZStd::unordered_set<AZStd::string>& excludedFolders,
|
||||
bool includeFolders,
|
||||
bool recursiveSearch) const
|
||||
{
|
||||
if (relativeName.isEmpty())
|
||||
{
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
QDir sourceFolderDir(sourceFolder);
|
||||
|
||||
QString posixRelativeName = QDir::fromNativeSeparators(relativeName);
|
||||
|
||||
QStringList returnList;
|
||||
QRegExp nameMatch{ posixRelativeName, Qt::CaseInsensitive, QRegExp::Wildcard };
|
||||
AZStd::stack<QString> dirs;
|
||||
dirs.push(sourceFolderDir.absolutePath());
|
||||
|
||||
while (!dirs.empty())
|
||||
{
|
||||
QString absolutePath = dirs.top();
|
||||
dirs.pop();
|
||||
|
||||
if (excludedFolders.contains(absolutePath.toUtf8().constData()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QDirIterator dirIterator(absolutePath, QDir::AllEntries | QDir::NoSymLinks | QDir::NoDotAndDotDot);
|
||||
|
||||
while (dirIterator.hasNext())
|
||||
{
|
||||
dirIterator.next();
|
||||
|
||||
if (!dirIterator.fileInfo().isFile())
|
||||
{
|
||||
if (recursiveSearch)
|
||||
{
|
||||
dirs.push(dirIterator.filePath());
|
||||
}
|
||||
|
||||
if (!includeFolders)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
QString pathMatch{ sourceFolderDir.relativeFilePath(dirIterator.filePath()) };
|
||||
if (nameMatch.exactMatch(pathMatch))
|
||||
{
|
||||
returnList.append(QDir::fromNativeSeparators(dirIterator.filePath()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return returnList;
|
||||
}
|
||||
|
||||
const AssetProcessor::ScanFolderInfo* PlatformConfiguration::GetScanFolderForFile(const QString& fullFileName) const
|
||||
{
|
||||
QString normalized = AssetUtilities::NormalizeFilePath(fullFileName);
|
||||
|
||||
@@ -256,6 +256,9 @@ namespace AssetProcessor
|
||||
//! Retrieve the scan folder at a given index.
|
||||
AssetProcessor::ScanFolderInfo& GetScanFolderAt(int index);
|
||||
|
||||
//! Retrieve the scan folder at a given index.
|
||||
const AssetProcessor::ScanFolderInfo& GetScanFolderAt(int index) const;
|
||||
|
||||
//! Manually add a scan folder. Also used for testing.
|
||||
void AddScanFolder(const AssetProcessor::ScanFolderInfo& source, bool isUnitTesting = false);
|
||||
|
||||
@@ -298,7 +301,16 @@ namespace AssetProcessor
|
||||
QString FindFirstMatchingFile(QString relativeName) const;
|
||||
|
||||
//! given a relative name with wildcard characters (* allowed) find a set of matching files or optionally folders
|
||||
QStringList FindWildcardMatches(const QString& sourceFolder, QString relativeName, bool includeFolders = false, bool recursiveSearch = true) const;
|
||||
QStringList FindWildcardMatches(const QString& sourceFolder, QString relativeName, bool includeFolders = false,
|
||||
bool recursiveSearch = true) const;
|
||||
|
||||
//! given a relative name with wildcard characters (* allowed) find a set of matching files or optionally folders
|
||||
QStringList FindWildcardMatches(
|
||||
const QString& sourceFolder,
|
||||
QString relativeName,
|
||||
const AZStd::unordered_set<AZStd::string>& excludedFolders,
|
||||
bool includeFolders = false,
|
||||
bool recursiveSearch = true) const;
|
||||
|
||||
//! given a fileName (as a full path), return the database source name which includes the output prefix.
|
||||
//!
|
||||
|
||||
@@ -1161,7 +1161,7 @@ namespace AssetUtilities
|
||||
{
|
||||
#ifndef AZ_TESTS_ENABLED
|
||||
// Only used for unit tests, speed is critical for GetFileHash.
|
||||
AZ_UNUSED(hashMsDelay);
|
||||
hashMsDelay = 0;
|
||||
#endif
|
||||
bool useFileHashing = ShouldUseFileHashing();
|
||||
|
||||
@@ -1170,10 +1170,10 @@ namespace AssetUtilities
|
||||
return 0;
|
||||
}
|
||||
|
||||
AZ::u64 hash = 0;
|
||||
if(!force)
|
||||
{
|
||||
auto* fileStateInterface = AZ::Interface<AssetProcessor::IFileStateRequests>::Get();
|
||||
AZ::u64 hash = 0;
|
||||
|
||||
if (fileStateInterface && fileStateInterface->GetHash(filePath, &hash))
|
||||
{
|
||||
@@ -1181,64 +1181,8 @@ namespace AssetUtilities
|
||||
}
|
||||
}
|
||||
|
||||
char buffer[FileHashBufferSize];
|
||||
|
||||
constexpr bool ErrorOnReadFailure = true;
|
||||
AZ::IO::FileIOStream readStream(filePath, AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary, ErrorOnReadFailure);
|
||||
|
||||
if(readStream.IsOpen() && readStream.CanRead())
|
||||
{
|
||||
AZ::IO::SizeType bytesRead;
|
||||
|
||||
auto* state = XXH64_createState();
|
||||
|
||||
if(state == nullptr)
|
||||
{
|
||||
AZ_Assert(false, "Failed to create hash state");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (XXH64_reset(state, 0) == XXH_ERROR)
|
||||
{
|
||||
AZ_Assert(false, "Failed to reset hash state");
|
||||
return 0;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
// In edge cases where another process is writing to this file while this hashing is occuring and that file wasn't locked,
|
||||
// the following read check can fail because it performs an end of file check, and asserts and shuts down if the read size
|
||||
// was smaller than the buffer and the read is not at the end of the file. The logic used to check end of file internal to read
|
||||
// will be out of date in the edge cases where another process is actively writing to this file while this hash is running.
|
||||
// The stream's length ends up more accurate in this case, preventing this assert and shut down.
|
||||
// One area this occurs is the navigation mesh file (mnmnavmission0.bai) that's temporarily created when exporting a level,
|
||||
// the navigation system can still be writing to this file when hashing begins, causing the EoF marker to change.
|
||||
AZ::IO::SizeType remainingToRead = AZStd::min(readStream.GetLength() - readStream.GetCurPos(), aznumeric_cast<AZ::IO::SizeType>(AZ_ARRAY_SIZE(buffer)));
|
||||
bytesRead = readStream.Read(remainingToRead, buffer);
|
||||
|
||||
if(bytesReadOut)
|
||||
{
|
||||
*bytesReadOut += bytesRead;
|
||||
}
|
||||
|
||||
XXH64_update(state, buffer, bytesRead);
|
||||
#ifdef AZ_TESTS_ENABLED
|
||||
// Used by unit tests to force the race condition mentioned above, to verify the crash fix.
|
||||
if(hashMsDelay > 0)
|
||||
{
|
||||
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(hashMsDelay));
|
||||
}
|
||||
#endif
|
||||
|
||||
} while (bytesRead > 0);
|
||||
|
||||
auto hash = XXH64_digest(state);
|
||||
|
||||
XXH64_freeState(state);
|
||||
|
||||
return hash;
|
||||
}
|
||||
return 0;
|
||||
hash = AssetBuilderSDK::GetFileHash(filePath, bytesReadOut, hashMsDelay);
|
||||
return hash;
|
||||
}
|
||||
|
||||
AZ::u64 AdjustTimestamp(QDateTime timestamp)
|
||||
|
||||
@@ -238,7 +238,6 @@ namespace AssetUtilities
|
||||
// hashMsDelay is only for automated tests to test that writing to a file while it's hashing does not cause a crash.
|
||||
// hashMsDelay is not used in non-unit test builds.
|
||||
AZ::u64 GetFileHash(const char* filePath, bool force = false, AZ::IO::SizeType* bytesReadOut = nullptr, int hashMsDelay = 0);
|
||||
inline constexpr AZ::u64 FileHashBufferSize = 1024 * 64;
|
||||
|
||||
//! Adjusts a timestamp to fix timezone settings and account for any precision adjustment needed
|
||||
AZ::u64 AdjustTimestamp(QDateTime timestamp);
|
||||
|
||||
@@ -23,25 +23,11 @@ namespace O3DE::ProjectManager
|
||||
QString cmakeGenerator = (whichNinjaResult.IsSuccess()) ? "Ninja Multi-Config" : "Unix Makefiles";
|
||||
bool compileProfileOnBuild = (whichNinjaResult.IsSuccess());
|
||||
|
||||
// On Linux the default compiler is gcc. For O3DE, it is clang, so we need to specify the version of clang that is detected
|
||||
// in order to get the compiler option.
|
||||
auto compilerOptionResult = ProjectUtils::FindSupportedCompilerForPlatform();
|
||||
if (!compilerOptionResult.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(compilerOptionResult.GetError());
|
||||
}
|
||||
auto clangCompilers = compilerOptionResult.GetValue().split('|');
|
||||
AZ_Assert(clangCompilers.length()==2, "Invalid clang compiler pair specification");
|
||||
|
||||
QString clangCompilerOption = clangCompilers[0];
|
||||
QString clangPPCompilerOption = clangCompilers[1];
|
||||
QString targetBuildPath = QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix);
|
||||
QStringList generateProjectArgs = QStringList{ProjectCMakeCommand,
|
||||
"-B", ProjectBuildPathPostfix,
|
||||
"-S", ".",
|
||||
QString("-G%1").arg(cmakeGenerator),
|
||||
QString("-DCMAKE_C_COMPILER=").append(clangCompilerOption),
|
||||
QString("-DCMAKE_CXX_COMPILER=").append(clangPPCompilerOption),
|
||||
QString("-DLY_3RDPARTY_PATH=").append(thirdPartyPath)};
|
||||
if (!compileProfileOnBuild)
|
||||
{
|
||||
|
||||
@@ -9,3 +9,4 @@
|
||||
#pragma once
|
||||
|
||||
#define AZ_TRAIT_PROJECT_MANAGER_CUSTOM_TITLEBAR false
|
||||
#define AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT false
|
||||
|
||||
@@ -17,13 +17,11 @@ namespace O3DE::ProjectManager
|
||||
namespace ProjectUtils
|
||||
{
|
||||
// The list of clang C/C++ compiler command lines to validate on the host Linux system
|
||||
const QStringList SupportedClangCommands = {"clang-12|clang++-12"};
|
||||
const QStringList SupportedClangVersions = {"13", "12", "11", "10", "9", "8", "7", "6.0"};
|
||||
|
||||
AZ::Outcome<QProcessEnvironment, QString> GetCommandLineProcessEnvironment()
|
||||
{
|
||||
QProcessEnvironment currentEnvironment(QProcessEnvironment::systemEnvironment());
|
||||
currentEnvironment.insert("CC", "clang-12");
|
||||
currentEnvironment.insert("CXX", "clang++-12");
|
||||
return AZ::Success(currentEnvironment);
|
||||
}
|
||||
|
||||
@@ -39,16 +37,13 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
|
||||
// Look for the first compatible version of clang. The list below will contain the known clang compilers that have been tested for O3DE.
|
||||
for (const QString& supportClangCommand : SupportedClangCommands)
|
||||
for (const QString& supportClangVersion : SupportedClangVersions)
|
||||
{
|
||||
auto clangCompilers = supportClangCommand.split('|');
|
||||
AZ_Assert(clangCompilers.length()==2, "Invalid clang compiler pair specification");
|
||||
|
||||
auto whichClangResult = ProjectUtils::ExecuteCommandResult("which", QStringList{clangCompilers[0]}, QProcessEnvironment::systemEnvironment());
|
||||
auto whichClangPPResult = ProjectUtils::ExecuteCommandResult("which", QStringList{clangCompilers[1]}, QProcessEnvironment::systemEnvironment());
|
||||
auto whichClangResult = ProjectUtils::ExecuteCommandResult("which", QStringList{QString("clang-%1").arg(supportClangVersion)}, QProcessEnvironment::systemEnvironment());
|
||||
auto whichClangPPResult = ProjectUtils::ExecuteCommandResult("which", QStringList{QString("clang++-%1").arg(supportClangVersion)}, QProcessEnvironment::systemEnvironment());
|
||||
if (whichClangResult.IsSuccess() && whichClangPPResult.IsSuccess())
|
||||
{
|
||||
return AZ::Success(supportClangCommand);
|
||||
return AZ::Success(QString("clang-%1").arg(supportClangVersion));
|
||||
}
|
||||
}
|
||||
return AZ::Failure(QObject::tr("Clang not found. <br><br>"
|
||||
@@ -101,5 +96,10 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
return AZ::Utils::GetExecutableDirectory();
|
||||
}
|
||||
|
||||
AZ::Outcome<QString, QString> CreateDesktopShortcut([[maybe_unused]] const QString& filename, [[maybe_unused]] const QString& targetPath, [[maybe_unused]] const QStringList& arguments)
|
||||
{
|
||||
return AZ::Failure(QObject::tr("Creating desktop shortcuts functionality not implemented for this platform yet."));
|
||||
}
|
||||
} // namespace ProjectUtils
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -9,3 +9,4 @@
|
||||
#pragma once
|
||||
|
||||
#define AZ_TRAIT_PROJECT_MANAGER_CUSTOM_TITLEBAR false
|
||||
#define AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT false
|
||||
|
||||
@@ -137,5 +137,10 @@ namespace O3DE::ProjectManager
|
||||
|
||||
return editorPath;
|
||||
}
|
||||
|
||||
AZ::Outcome<QString, QString> CreateDesktopShortcut([[maybe_unused]] const QString& filename, [[maybe_unused]] const QString& targetPath, [[maybe_unused]] const QStringList& arguments)
|
||||
{
|
||||
return AZ::Failure(QObject::tr("Creating desktop shortcuts functionality not implemented for this platform yet."));
|
||||
}
|
||||
} // namespace ProjectUtils
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -9,3 +9,4 @@
|
||||
#pragma once
|
||||
|
||||
#define AZ_TRAIT_PROJECT_MANAGER_CUSTOM_TITLEBAR true
|
||||
#define AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT true
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <QFileInfo>
|
||||
#include <QProcess>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
|
||||
@@ -146,5 +147,26 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
return AZ::Utils::GetExecutableDirectory();
|
||||
}
|
||||
|
||||
AZ::Outcome<QString, QString> CreateDesktopShortcut(const QString& filename, const QString& targetPath, const QStringList& arguments)
|
||||
{
|
||||
const QString cmd{"powershell.exe"};
|
||||
const QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
||||
const QString shortcutPath = QString("%1/%2.lnk").arg(desktopPath).arg(filename);
|
||||
const QString arg = QString("$s=(New-Object -COM WScript.Shell).CreateShortcut('%1');$s.TargetPath='%2';$s.Arguments='%3';$s.Save();")
|
||||
.arg(shortcutPath)
|
||||
.arg(targetPath)
|
||||
.arg(arguments.join(' '));
|
||||
auto createShortcutResult = ExecuteCommandResult(cmd, QStringList{"-Command", arg}, QProcessEnvironment::systemEnvironment());
|
||||
if (!createShortcutResult.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(QObject::tr("Failed to create desktop shortcut %1 <br><br>"
|
||||
"Please verify you have permission to create files at the specified location.<br><br> %2")
|
||||
.arg(shortcutPath)
|
||||
.arg(createShortcutResult.GetError()));
|
||||
}
|
||||
|
||||
return AZ::Success(QObject::tr("Desktop shortcut created at<br><a href=\"%1\">%2</a>").arg(desktopPath).arg(shortcutPath));
|
||||
}
|
||||
} // namespace ProjectUtils
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:342c3eaccf68a178dfd8c2b1792a93a8c9197c8184dca11bf90706d7481df087
|
||||
size 1611268
|
||||
oid sha256:e9ad0383f3b917fa7f4efa307a8e109a70bb5f66deb197189d013f60eb8dc32c
|
||||
size 1010250
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:797794816e4b1702f1ae1f32b408c95c79eb1f8a95aba43cfad9cccc181b0bda
|
||||
size 1135182
|
||||
oid sha256:84aab95ec8a5e3ba6ecb3aff1a814afc3171a937aa658decd18c2740623bd172
|
||||
size 984146
|
||||
|
||||
@@ -41,5 +41,6 @@
|
||||
<file>Download.svg</file>
|
||||
<file>in_progress.gif</file>
|
||||
<file>gem.svg</file>
|
||||
<file>checkmark.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -563,6 +563,52 @@ QProgressBar::chunk {
|
||||
margin-top:5px;
|
||||
}
|
||||
|
||||
#gemCatalogUpdateGemButton,
|
||||
#gemCatalogUninstallGemButton
|
||||
{
|
||||
qproperty-flat: true;
|
||||
min-height:24px;
|
||||
max-height:24px;
|
||||
border-radius: 3px;
|
||||
text-align:center;
|
||||
font-size:12px;
|
||||
font-weight:600;
|
||||
}
|
||||
|
||||
#gemCatalogUpdateGemButton {
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #888888, stop: 1.0 #555555);
|
||||
}
|
||||
#gemCatalogUpdateGemButton:hover {
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #999999, stop: 1.0 #666666);
|
||||
}
|
||||
#gemCatalogUpdateGemButton:pressed {
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #555555, stop: 1.0 #777777);
|
||||
}
|
||||
|
||||
#footer > #gemCatalogUninstallGemButton,
|
||||
#gemCatalogUninstallGemButton {
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #E32C27, stop: 1.0 #951D21);
|
||||
}
|
||||
#footer > #gemCatalogUninstallGemButton:hover,
|
||||
#gemCatalogUninstallGemButton:hover {
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #FD3129, stop: 1.0 #AF2221);
|
||||
}
|
||||
#footer > #gemCatalogUninstallGemButton:pressed,
|
||||
#gemCatalogUninstallGemButton:pressed {
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #951D1F, stop: 1.0 #C92724);
|
||||
}
|
||||
|
||||
#gemCatalogDialogSubTitle {
|
||||
font-size:14px;
|
||||
font-weight:600;
|
||||
}
|
||||
|
||||
/************** Filter Tag widget **************/
|
||||
|
||||
#FilterTagWidgetTextLabel {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="15px" height="14px" viewBox="0 0 15 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Icons / Hub / Download Copy 5</title>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Screen-1-Copy-80" transform="translate(-425.000000, -251.000000)">
|
||||
<g id="Icons-/-Hub-/-Download-Copy-5" transform="translate(424.573941, 250.098705)">
|
||||
<rect id="Icon-Background" x="0" y="0" width="16" height="16"></rect>
|
||||
<path d="M8,1.33333333 C11.6818983,1.33333333 14.6666667,4.31810167 14.6666667,8 C14.6666667,11.6818983 11.6818983,14.6666667 8,14.6666667 C4.31810167,14.6666667 1.33333333,11.6818983 1.33333333,8 C1.33333333,4.31810167 4.31810167,1.33333333 8,1.33333333 Z M12.0947571,4 L5.96649831,10.1282588 L3.60947571,7.77123617 L2.66666667,8.71404521 L5.96649831,12.0138769 L13.0375661,4.94280904 L12.0947571,4 Z" id="Combined-Shape" fill="#58BC61"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -15,6 +15,7 @@
|
||||
#include <GemCatalog/GemCatalogScreen.h>
|
||||
#include <GemRepo/GemRepoScreen.h>
|
||||
#include <ProjectUtils.h>
|
||||
#include <DownloadController.h>
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
@@ -18,7 +18,6 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
DownloadController::DownloadController(QWidget* parent)
|
||||
: QObject()
|
||||
, m_lastProgress(0)
|
||||
, m_parent(parent)
|
||||
{
|
||||
m_worker = new DownloadWorker();
|
||||
@@ -41,9 +40,11 @@ namespace O3DE::ProjectManager
|
||||
void DownloadController::AddGemDownload(const QString& gemName)
|
||||
{
|
||||
m_gemNames.push_back(gemName);
|
||||
emit GemDownloadAdded(gemName);
|
||||
|
||||
if (m_gemNames.size() == 1)
|
||||
{
|
||||
m_worker->SetGemToDownload(m_gemNames[0], false);
|
||||
m_worker->SetGemToDownload(m_gemNames.front(), false);
|
||||
m_workerThread.start();
|
||||
}
|
||||
}
|
||||
@@ -62,29 +63,42 @@ namespace O3DE::ProjectManager
|
||||
else
|
||||
{
|
||||
m_gemNames.erase(findResult);
|
||||
emit GemDownloadRemoved(gemName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadController::UpdateUIProgress(int progress)
|
||||
void DownloadController::UpdateUIProgress(int bytesDownloaded, int totalBytes)
|
||||
{
|
||||
m_lastProgress = progress;
|
||||
emit GemDownloadProgress(progress);
|
||||
emit GemDownloadProgress(m_gemNames.front(), bytesDownloaded, totalBytes);
|
||||
}
|
||||
|
||||
void DownloadController::HandleResults(const QString& result)
|
||||
void DownloadController::HandleResults(const QString& result, const QString& detailedError)
|
||||
{
|
||||
bool succeeded = true;
|
||||
|
||||
if (!result.isEmpty())
|
||||
{
|
||||
QMessageBox::critical(nullptr, tr("Gem download"), result);
|
||||
if (!detailedError.isEmpty())
|
||||
{
|
||||
QMessageBox gemDownloadError;
|
||||
gemDownloadError.setIcon(QMessageBox::Critical);
|
||||
gemDownloadError.setWindowTitle(tr("Gem download"));
|
||||
gemDownloadError.setText(result);
|
||||
gemDownloadError.setDetailedText(detailedError);
|
||||
gemDownloadError.exec();
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(nullptr, tr("Gem download"), result);
|
||||
}
|
||||
succeeded = false;
|
||||
}
|
||||
|
||||
QString gemName = m_gemNames.front();
|
||||
m_gemNames.erase(m_gemNames.begin());
|
||||
emit Done(gemName, succeeded);
|
||||
emit GemDownloadRemoved(gemName);
|
||||
|
||||
if (!m_gemNames.empty())
|
||||
{
|
||||
|
||||
@@ -53,20 +53,20 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
public slots:
|
||||
void UpdateUIProgress(int progress);
|
||||
void HandleResults(const QString& result);
|
||||
void UpdateUIProgress(int bytesDownloaded, int totalBytes);
|
||||
void HandleResults(const QString& result, const QString& detailedError);
|
||||
|
||||
signals:
|
||||
void StartGemDownload(const QString& gemName);
|
||||
void Done(const QString& gemName, bool success = true);
|
||||
void GemDownloadProgress(int percentage);
|
||||
void GemDownloadAdded(const QString& gemName);
|
||||
void GemDownloadRemoved(const QString& gemName);
|
||||
void GemDownloadProgress(const QString& gemName, int bytesDownloaded, int totalBytes);
|
||||
|
||||
private:
|
||||
DownloadWorker* m_worker;
|
||||
QThread m_workerThread;
|
||||
QWidget* m_parent;
|
||||
AZStd::vector<QString> m_gemNames;
|
||||
|
||||
int m_lastProgress;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -20,19 +20,20 @@ namespace O3DE::ProjectManager
|
||||
|
||||
void DownloadWorker::StartDownload()
|
||||
{
|
||||
auto gemDownloadProgress = [=](int downloadProgress)
|
||||
auto gemDownloadProgress = [=](int bytesDownloaded, int totalBytes)
|
||||
{
|
||||
m_downloadProgress = downloadProgress;
|
||||
emit UpdateProgress(downloadProgress);
|
||||
emit UpdateProgress(bytesDownloaded, totalBytes);
|
||||
};
|
||||
AZ::Outcome<void, AZStd::string> gemInfoResult = PythonBindingsInterface::Get()->DownloadGem(m_gemName, gemDownloadProgress);
|
||||
AZ::Outcome<void, AZStd::pair<AZStd::string, AZStd::string>> gemInfoResult =
|
||||
PythonBindingsInterface::Get()->DownloadGem(m_gemName, gemDownloadProgress, /*force*/true);
|
||||
|
||||
if (gemInfoResult.IsSuccess())
|
||||
{
|
||||
emit Done("");
|
||||
emit Done("", "");
|
||||
}
|
||||
else
|
||||
{
|
||||
emit Done(tr("Gem download failed"));
|
||||
emit Done(gemInfoResult.GetError().first.c_str(), gemInfoResult.GetError().second.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,12 +31,11 @@ namespace O3DE::ProjectManager
|
||||
void SetGemToDownload(const QString& gemName, bool downloadNow = true);
|
||||
|
||||
signals:
|
||||
void UpdateProgress(int progress);
|
||||
void Done(QString result = "");
|
||||
void UpdateProgress(int bytesDownloaded, int totalBytes);
|
||||
void Done(QString result = "", QString detailedResult = "");
|
||||
|
||||
private:
|
||||
|
||||
QString m_gemName;
|
||||
int m_downloadProgress;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -72,12 +72,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
bool EngineScreenCtrl::ContainsScreen(ProjectManagerScreen screen)
|
||||
{
|
||||
if (screen == m_engineSettingsScreen->GetScreenEnum() || screen == m_gemRepoScreen->GetScreenEnum())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return screen == m_engineSettingsScreen->GetScreenEnum() || screen == m_gemRepoScreen->GetScreenEnum();
|
||||
}
|
||||
|
||||
void EngineScreenCtrl::NotifyCurrentScreen()
|
||||
|
||||
@@ -7,29 +7,39 @@
|
||||
*/
|
||||
|
||||
#include <GemCatalog/GemCatalogHeaderWidget.h>
|
||||
#include <TagWidget.h>
|
||||
|
||||
#include <AzCore/std/functional.h>
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QMouseEvent>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QProgressBar>
|
||||
#include <TagWidget.h>
|
||||
#include <QMenu>
|
||||
#include <QLocale>
|
||||
#include <QMovie>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
CartOverlayWidget::CartOverlayWidget(GemModel* gemModel, DownloadController* downloadController, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
GemCartWidget::GemCartWidget(GemModel* gemModel, DownloadController* downloadController, QWidget* parent)
|
||||
: QScrollArea(parent)
|
||||
, m_gemModel(gemModel)
|
||||
, m_downloadController(downloadController)
|
||||
{
|
||||
setObjectName("GemCatalogCart");
|
||||
setWidgetResizable(true);
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
|
||||
m_layout = new QVBoxLayout();
|
||||
m_layout->setSpacing(0);
|
||||
m_layout->setMargin(5);
|
||||
m_layout->setAlignment(Qt::AlignTop);
|
||||
setLayout(m_layout);
|
||||
setMinimumHeight(400);
|
||||
|
||||
QHBoxLayout* hLayout = new QHBoxLayout();
|
||||
|
||||
@@ -115,11 +125,15 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
return dependencies;
|
||||
});
|
||||
|
||||
setWindowFlags(Qt::FramelessWindowHint | Qt::Dialog);
|
||||
}
|
||||
|
||||
void CartOverlayWidget::CreateGemSection(const QString& singularTitle, const QString& pluralTitle, GetTagIndicesCallback getTagIndices)
|
||||
GemCartWidget::~GemCartWidget()
|
||||
{
|
||||
// disconnect from all download controller signals
|
||||
disconnect(m_downloadController, nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
void GemCartWidget::CreateGemSection(const QString& singularTitle, const QString& pluralTitle, GetTagIndicesCallback getTagIndices)
|
||||
{
|
||||
QWidget* widget = new QWidget();
|
||||
widget->setFixedWidth(s_width);
|
||||
@@ -155,20 +169,20 @@ namespace O3DE::ProjectManager
|
||||
update();
|
||||
}
|
||||
|
||||
void CartOverlayWidget::OnCancelDownloadActivated(const QString& gemName)
|
||||
void GemCartWidget::OnCancelDownloadActivated(const QString& gemName)
|
||||
{
|
||||
m_downloadController->CancelGemDownload(gemName);
|
||||
}
|
||||
|
||||
void CartOverlayWidget::CreateDownloadSection()
|
||||
void GemCartWidget::CreateDownloadSection()
|
||||
{
|
||||
QWidget* widget = new QWidget();
|
||||
widget->setFixedWidth(s_width);
|
||||
m_layout->addWidget(widget);
|
||||
m_downloadSectionWidget = new QWidget();
|
||||
m_downloadSectionWidget->setFixedWidth(s_width);
|
||||
m_layout->addWidget(m_downloadSectionWidget);
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
layout->setAlignment(Qt::AlignTop);
|
||||
widget->setLayout(layout);
|
||||
m_downloadSectionWidget->setLayout(layout);
|
||||
|
||||
QLabel* titleLabel = new QLabel();
|
||||
titleLabel->setObjectName("GemCatalogCartOverlaySectionLabel");
|
||||
@@ -187,91 +201,135 @@ namespace O3DE::ProjectManager
|
||||
QLabel* processingQueueLabel = new QLabel("Processing Queue");
|
||||
gemDownloadLayout->addWidget(processingQueueLabel);
|
||||
|
||||
QWidget* downloadingItemWidget = new QWidget();
|
||||
downloadingItemWidget->setObjectName("GemCatalogCartOverlayGemDownloadBG");
|
||||
gemDownloadLayout->addWidget(downloadingItemWidget);
|
||||
m_downloadingListWidget = new QWidget();
|
||||
m_downloadingListWidget->setObjectName("GemCatalogCartOverlayGemDownloadBG");
|
||||
gemDownloadLayout->addWidget(m_downloadingListWidget);
|
||||
QVBoxLayout* downloadingItemLayout = new QVBoxLayout();
|
||||
downloadingItemLayout->setAlignment(Qt::AlignTop);
|
||||
downloadingItemWidget->setLayout(downloadingItemLayout);
|
||||
m_downloadingListWidget->setLayout(downloadingItemLayout);
|
||||
|
||||
auto update = [=](int downloadProgress)
|
||||
QLabel* downloadsInProgessLabel = new QLabel("");
|
||||
downloadsInProgessLabel->setObjectName("NumDownloadsInProgressLabel");
|
||||
downloadingItemLayout->addWidget(downloadsInProgessLabel);
|
||||
|
||||
if (m_downloadController->IsDownloadQueueEmpty())
|
||||
{
|
||||
if (m_downloadController->IsDownloadQueueEmpty())
|
||||
m_downloadSectionWidget->hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Setup gem download rows for gems that are already in the queue
|
||||
const AZStd::vector<QString>& downloadQueue = m_downloadController->GetDownloadQueue();
|
||||
|
||||
for (const QString& gemName : downloadQueue)
|
||||
{
|
||||
widget->hide();
|
||||
GemDownloadAdded(gemName);
|
||||
}
|
||||
}
|
||||
|
||||
// connect to download controller data changed
|
||||
connect(m_downloadController, &DownloadController::GemDownloadAdded, this, &GemCartWidget::GemDownloadAdded);
|
||||
connect(m_downloadController, &DownloadController::GemDownloadRemoved, this, &GemCartWidget::GemDownloadRemoved);
|
||||
connect(m_downloadController, &DownloadController::GemDownloadProgress, this, &GemCartWidget::GemDownloadProgress);
|
||||
}
|
||||
|
||||
void GemCartWidget::GemDownloadAdded(const QString& gemName)
|
||||
{
|
||||
// Containing widget for the current download item
|
||||
QWidget* newGemDownloadWidget = new QWidget();
|
||||
newGemDownloadWidget->setObjectName(gemName);
|
||||
QVBoxLayout* downloadingGemLayout = new QVBoxLayout(newGemDownloadWidget);
|
||||
newGemDownloadWidget->setLayout(downloadingGemLayout);
|
||||
|
||||
// Gem name, progress string, cancel
|
||||
QHBoxLayout* nameProgressLayout = new QHBoxLayout(newGemDownloadWidget);
|
||||
TagWidget* newTag = new TagWidget({gemName, gemName}, newGemDownloadWidget);
|
||||
nameProgressLayout->addWidget(newTag);
|
||||
QLabel* progress = new QLabel(tr("Queued"), newGemDownloadWidget);
|
||||
progress->setObjectName("DownloadProgressLabel");
|
||||
nameProgressLayout->addWidget(progress);
|
||||
nameProgressLayout->addStretch();
|
||||
QLabel* cancelText = new QLabel(tr("<a href=\"%1\">Cancel</a>").arg(gemName), newGemDownloadWidget);
|
||||
cancelText->setTextInteractionFlags(Qt::LinksAccessibleByMouse);
|
||||
connect(cancelText, &QLabel::linkActivated, this, &GemCartWidget::OnCancelDownloadActivated);
|
||||
nameProgressLayout->addWidget(cancelText);
|
||||
downloadingGemLayout->addLayout(nameProgressLayout);
|
||||
|
||||
// Progress bar
|
||||
QProgressBar* downloadProgessBar = new QProgressBar(newGemDownloadWidget);
|
||||
downloadProgessBar->setObjectName("DownloadProgressBar");
|
||||
downloadingGemLayout->addWidget(downloadProgessBar);
|
||||
downloadProgessBar->setValue(0);
|
||||
|
||||
m_downloadingListWidget->layout()->addWidget(newGemDownloadWidget);
|
||||
|
||||
const AZStd::vector<QString>& downloadQueue = m_downloadController->GetDownloadQueue();
|
||||
QLabel* numDownloads = m_downloadingListWidget->findChild<QLabel*>("NumDownloadsInProgressLabel");
|
||||
numDownloads->setText(QString("%1 %2")
|
||||
.arg(downloadQueue.size())
|
||||
.arg(downloadQueue.size() == 1 ? tr("download in progress...") : tr("downloads in progress...")));
|
||||
|
||||
m_downloadingListWidget->show();
|
||||
}
|
||||
|
||||
void GemCartWidget::GemDownloadRemoved(const QString& gemName)
|
||||
{
|
||||
QWidget* gemToRemove = m_downloadingListWidget->findChild<QWidget*>(gemName);
|
||||
if (gemToRemove)
|
||||
{
|
||||
gemToRemove->deleteLater();
|
||||
}
|
||||
|
||||
if (m_downloadController->IsDownloadQueueEmpty())
|
||||
{
|
||||
m_downloadSectionWidget->hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t downloadQueueSize = m_downloadController->GetDownloadQueue().size();
|
||||
QLabel* numDownloads = m_downloadingListWidget->findChild<QLabel*>("NumDownloadsInProgressLabel");
|
||||
numDownloads->setText(QString("%1 %2")
|
||||
.arg(downloadQueueSize)
|
||||
.arg(downloadQueueSize == 1 ? tr("download in progress...") : tr("downloads in progress...")));
|
||||
}
|
||||
}
|
||||
|
||||
void GemCartWidget::GemDownloadProgress(const QString& gemName, int bytesDownloaded, int totalBytes)
|
||||
{
|
||||
QWidget* gemToUpdate = m_downloadingListWidget->findChild<QWidget*>(gemName);
|
||||
if (gemToUpdate)
|
||||
{
|
||||
QLabel* progressLabel = gemToUpdate->findChild<QLabel*>("DownloadProgressLabel");
|
||||
QProgressBar* progressBar = gemToUpdate->findChild<QProgressBar*>("DownloadProgressBar");
|
||||
|
||||
// totalBytes can be 0 if the server does not return a content-length for the object
|
||||
if (totalBytes != 0)
|
||||
{
|
||||
int downloadPercentage = static_cast<int>((bytesDownloaded / static_cast<float>(totalBytes)) * 100);
|
||||
if (progressLabel)
|
||||
{
|
||||
progressLabel->setText(QString("%1%").arg(downloadPercentage));
|
||||
}
|
||||
if (progressBar)
|
||||
{
|
||||
progressBar->setValue(downloadPercentage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
widget->setUpdatesEnabled(false);
|
||||
// remove items
|
||||
QLayoutItem* layoutItem = nullptr;
|
||||
while ((layoutItem = downloadingItemLayout->takeAt(0)) != nullptr)
|
||||
if (progressLabel)
|
||||
{
|
||||
if (layoutItem->layout())
|
||||
{
|
||||
// Gem info row
|
||||
QLayoutItem* rowLayoutItem = nullptr;
|
||||
while ((rowLayoutItem = layoutItem->layout()->takeAt(0)) != nullptr)
|
||||
{
|
||||
rowLayoutItem->widget()->deleteLater();
|
||||
}
|
||||
layoutItem->layout()->deleteLater();
|
||||
}
|
||||
if (layoutItem->widget())
|
||||
{
|
||||
layoutItem->widget()->deleteLater();
|
||||
}
|
||||
progressLabel->setText(QLocale::system().formattedDataSize(bytesDownloaded));
|
||||
}
|
||||
|
||||
// Setup gem download rows
|
||||
const AZStd::vector<QString>& downloadQueue = m_downloadController->GetDownloadQueue();
|
||||
|
||||
QLabel* downloadsInProgessLabel = new QLabel("");
|
||||
downloadsInProgessLabel->setText(
|
||||
QString("%1 %2").arg(downloadQueue.size()).arg(downloadQueue.size() == 1 ? tr("download in progress...") : tr("downloads in progress...")));
|
||||
downloadingItemLayout->addWidget(downloadsInProgessLabel);
|
||||
|
||||
for (int downloadingGemNumber = 0; downloadingGemNumber < downloadQueue.size(); ++downloadingGemNumber)
|
||||
if (progressBar)
|
||||
{
|
||||
QHBoxLayout* nameProgressLayout = new QHBoxLayout();
|
||||
|
||||
const QString& gemName = downloadQueue[downloadingGemNumber];
|
||||
TagWidget* newTag = new TagWidget({gemName, gemName});
|
||||
nameProgressLayout->addWidget(newTag);
|
||||
|
||||
QLabel* progress = new QLabel(downloadingGemNumber == 0? QString("%1%").arg(downloadProgress) : tr("Queued"));
|
||||
nameProgressLayout->addWidget(progress);
|
||||
|
||||
QSpacerItem* spacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
nameProgressLayout->addSpacerItem(spacer);
|
||||
|
||||
QLabel* cancelText = new QLabel(QString("<a href=\"%1\">Cancel</a>").arg(gemName));
|
||||
cancelText->setTextInteractionFlags(Qt::LinksAccessibleByMouse);
|
||||
connect(cancelText, &QLabel::linkActivated, this, &CartOverlayWidget::OnCancelDownloadActivated);
|
||||
nameProgressLayout->addWidget(cancelText);
|
||||
downloadingItemLayout->addLayout(nameProgressLayout);
|
||||
|
||||
QProgressBar* downloadProgessBar = new QProgressBar();
|
||||
downloadingItemLayout->addWidget(downloadProgessBar);
|
||||
downloadProgessBar->setValue(downloadingGemNumber == 0 ? downloadProgress : 0);
|
||||
progressBar->setRange(0, 0);
|
||||
}
|
||||
|
||||
widget->setUpdatesEnabled(true);
|
||||
widget->show();
|
||||
}
|
||||
};
|
||||
|
||||
auto downloadEnded = [=](const QString& /*gemName*/, bool /*success*/)
|
||||
{
|
||||
update(0); // update the list to remove the gem that has finished
|
||||
};
|
||||
// connect to download controller data changed
|
||||
connect(m_downloadController, &DownloadController::GemDownloadProgress, this, update);
|
||||
connect(m_downloadController, &DownloadController::Done, this, downloadEnded);
|
||||
update(0);
|
||||
}
|
||||
}
|
||||
|
||||
QVector<Tag> CartOverlayWidget::GetTagsFromModelIndices(const QVector<QModelIndex>& gems) const
|
||||
QVector<Tag> GemCartWidget::GetTagsFromModelIndices(const QVector<QModelIndex>& gems) const
|
||||
{
|
||||
QVector<Tag> tags;
|
||||
tags.reserve(gems.size());
|
||||
@@ -296,7 +354,7 @@ namespace O3DE::ProjectManager
|
||||
iconButton->setFocusPolicy(Qt::NoFocus);
|
||||
iconButton->setIcon(QIcon(":/Summary.svg"));
|
||||
iconButton->setFixedSize(s_iconSize, s_iconSize);
|
||||
connect(iconButton, &QPushButton::clicked, this, &CartButton::ShowOverlay);
|
||||
connect(iconButton, &QPushButton::clicked, this, &CartButton::ShowGemCart);
|
||||
m_layout->addWidget(iconButton);
|
||||
|
||||
m_countLabel = new QLabel();
|
||||
@@ -309,7 +367,7 @@ namespace O3DE::ProjectManager
|
||||
m_dropDownButton->setFocusPolicy(Qt::NoFocus);
|
||||
m_dropDownButton->setIcon(QIcon(":/CarrotArrowDown.svg"));
|
||||
m_dropDownButton->setFixedSize(s_arrowDownIconSize, s_arrowDownIconSize);
|
||||
connect(m_dropDownButton, &QPushButton::clicked, this, &CartButton::ShowOverlay);
|
||||
connect(m_dropDownButton, &QPushButton::clicked, this, &CartButton::ShowGemCart);
|
||||
m_layout->addWidget(m_dropDownButton);
|
||||
|
||||
// Adjust the label text whenever the model gets updated.
|
||||
@@ -324,72 +382,69 @@ namespace O3DE::ProjectManager
|
||||
m_dropDownButton->setVisible(!toBeAdded.isEmpty() || !toBeRemoved.isEmpty());
|
||||
|
||||
// Automatically close the overlay window in case there are no gems to be activated or deactivated anymore.
|
||||
if (m_cartOverlay && toBeAdded.isEmpty() && toBeRemoved.isEmpty())
|
||||
if (m_gemCart && toBeAdded.isEmpty() && toBeRemoved.isEmpty())
|
||||
{
|
||||
m_cartOverlay->deleteLater();
|
||||
m_cartOverlay = nullptr;
|
||||
m_gemCart->deleteLater();
|
||||
m_gemCart = nullptr;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CartButton::mousePressEvent([[maybe_unused]] QMouseEvent* event)
|
||||
{
|
||||
ShowOverlay();
|
||||
ShowGemCart();
|
||||
}
|
||||
|
||||
void CartButton::hideEvent(QHideEvent*)
|
||||
{
|
||||
if (m_cartOverlay)
|
||||
if (m_gemCart)
|
||||
{
|
||||
m_cartOverlay->hide();
|
||||
m_gemCart->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void CartButton::ShowOverlay()
|
||||
void CartButton::ShowGemCart()
|
||||
{
|
||||
const QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/true);
|
||||
const QVector<QModelIndex> toBeRemoved = m_gemModel->GatherGemsToBeRemoved(/*includeDependencies=*/true);
|
||||
if (toBeAdded.isEmpty() && toBeRemoved.isEmpty())
|
||||
if (toBeAdded.isEmpty() && toBeRemoved.isEmpty() && m_downloadController->IsDownloadQueueEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_cartOverlay)
|
||||
if (m_gemCart)
|
||||
{
|
||||
// Directly delete the former overlay before creating the new one.
|
||||
// Don't use deleteLater() here. This might overwrite the new overlay pointer
|
||||
// depending on the event queue.
|
||||
delete m_cartOverlay;
|
||||
delete m_gemCart;
|
||||
}
|
||||
|
||||
m_cartOverlay = new CartOverlayWidget(m_gemModel, m_downloadController, this);
|
||||
connect(m_cartOverlay, &QWidget::destroyed, this, [=]
|
||||
m_gemCart = new GemCartWidget(m_gemModel, m_downloadController, this);
|
||||
connect(m_gemCart, &QWidget::destroyed, this, [=]
|
||||
{
|
||||
// Reset the overlay pointer on destruction to prevent dangling pointers.
|
||||
m_cartOverlay = nullptr;
|
||||
m_gemCart = nullptr;
|
||||
// Tell header gem cart is no longer open
|
||||
UpdateGemCart(nullptr);
|
||||
});
|
||||
m_cartOverlay->show();
|
||||
m_gemCart->show();
|
||||
|
||||
const QPoint parentPos = m_dropDownButton->mapToParent(m_dropDownButton->pos());
|
||||
const QPoint globalPos = m_dropDownButton->mapToGlobal(m_dropDownButton->pos());
|
||||
const QPoint offset(-4, 10);
|
||||
m_cartOverlay->setGeometry(globalPos.x() - parentPos.x() - m_cartOverlay->width() + width() + offset.x(),
|
||||
globalPos.y() + offset.y(),
|
||||
m_cartOverlay->width(),
|
||||
m_cartOverlay->height());
|
||||
emit UpdateGemCart(m_gemCart);
|
||||
}
|
||||
|
||||
CartButton::~CartButton()
|
||||
{
|
||||
// Make sure the overlay window is automatically closed in case the gem catalog is destroyed.
|
||||
if (m_cartOverlay)
|
||||
if (m_gemCart)
|
||||
{
|
||||
m_cartOverlay->deleteLater();
|
||||
m_gemCart->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
GemCatalogHeaderWidget::GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, DownloadController* downloadController, QWidget* parent)
|
||||
: QFrame(parent)
|
||||
, m_downloadController(downloadController)
|
||||
{
|
||||
QHBoxLayout* hLayout = new QHBoxLayout();
|
||||
hLayout->setAlignment(Qt::AlignLeft);
|
||||
@@ -416,8 +471,25 @@ namespace O3DE::ProjectManager
|
||||
hLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding));
|
||||
hLayout->addSpacerItem(new QSpacerItem(75, 0, QSizePolicy::Fixed));
|
||||
|
||||
CartButton* cartButton = new CartButton(gemModel, downloadController);
|
||||
hLayout->addWidget(cartButton);
|
||||
// spinner
|
||||
m_downloadSpinnerMovie = new QMovie(":/in_progress.gif");
|
||||
m_downloadSpinner = new QLabel(this);
|
||||
m_downloadSpinner->setScaledContents(true);
|
||||
m_downloadSpinner->setMaximumSize(16, 16);
|
||||
m_downloadSpinner->setMovie(m_downloadSpinnerMovie);
|
||||
hLayout->addWidget(m_downloadSpinner);
|
||||
hLayout->addSpacing(8);
|
||||
|
||||
// downloading label
|
||||
m_downloadLabel = new QLabel(tr("Downloading"));
|
||||
hLayout->addWidget(m_downloadLabel);
|
||||
m_downloadSpinner->hide();
|
||||
m_downloadLabel->hide();
|
||||
|
||||
hLayout->addSpacing(16);
|
||||
|
||||
m_cartButton = new CartButton(gemModel, downloadController);
|
||||
hLayout->addWidget(m_cartButton);
|
||||
hLayout->addSpacing(16);
|
||||
|
||||
// Separating line
|
||||
@@ -429,6 +501,7 @@ namespace O3DE::ProjectManager
|
||||
hLayout->addSpacing(16);
|
||||
|
||||
QMenu* gemMenu = new QMenu(this);
|
||||
gemMenu->addAction( tr("Refresh"), [this]() { emit RefreshGems(); });
|
||||
gemMenu->addAction( tr("Show Gem Repos"), [this]() { emit OpenGemsRepo(); });
|
||||
gemMenu->addSeparator();
|
||||
gemMenu->addAction( tr("Add Existing Gem"), [this]() { emit AddGem(); });
|
||||
@@ -439,10 +512,78 @@ namespace O3DE::ProjectManager
|
||||
gemMenuButton->setIcon(QIcon(":/menu.svg"));
|
||||
gemMenuButton->setIconSize(QSize(36, 24));
|
||||
hLayout->addWidget(gemMenuButton);
|
||||
|
||||
connect(m_downloadController, &DownloadController::GemDownloadAdded, this, &GemCatalogHeaderWidget::GemDownloadAdded);
|
||||
connect(m_downloadController, &DownloadController::GemDownloadRemoved, this, &GemCatalogHeaderWidget::GemDownloadRemoved);
|
||||
|
||||
connect(
|
||||
m_cartButton, &CartButton::UpdateGemCart, this,
|
||||
[this](QWidget* gemCart)
|
||||
{
|
||||
GemCartShown(gemCart);
|
||||
if (gemCart)
|
||||
{
|
||||
emit UpdateGemCart(gemCart);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void GemCatalogHeaderWidget::GemDownloadAdded(const QString& /*gemName*/)
|
||||
{
|
||||
m_downloadSpinner->show();
|
||||
m_downloadLabel->show();
|
||||
m_downloadSpinnerMovie->start();
|
||||
m_cartButton->ShowGemCart();
|
||||
}
|
||||
|
||||
void GemCatalogHeaderWidget::GemDownloadRemoved(const QString& /*gemName*/)
|
||||
{
|
||||
if (m_downloadController->IsDownloadQueueEmpty())
|
||||
{
|
||||
m_downloadSpinner->hide();
|
||||
m_downloadLabel->hide();
|
||||
m_downloadSpinnerMovie->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void GemCatalogHeaderWidget::GemCartShown(bool state)
|
||||
{
|
||||
m_showGemCart = state;
|
||||
repaint();
|
||||
}
|
||||
|
||||
void GemCatalogHeaderWidget::ReinitForProject()
|
||||
{
|
||||
m_filterLineEdit->setText({});
|
||||
}
|
||||
|
||||
void GemCatalogHeaderWidget::paintEvent([[maybe_unused]] QPaintEvent* event)
|
||||
{
|
||||
// Only show triangle when cart is shown
|
||||
if (!m_showGemCart)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const QPoint buttonPos = m_cartButton->pos();
|
||||
const QSize buttonSize = m_cartButton->size();
|
||||
|
||||
// Draw isosceles triangle with top point touching bottom of cartButton
|
||||
// Bottom aligned with header bottom and top of right panel
|
||||
const QPoint topPoint(buttonPos.x() + buttonSize.width() / 2, buttonPos.y() + buttonSize.height());
|
||||
const QPoint bottomLeftPoint(topPoint.x() - 20, height());
|
||||
const QPoint bottomRightPoint(topPoint.x() + 20, height());
|
||||
|
||||
QPainterPath trianglePath;
|
||||
trianglePath.moveTo(topPoint);
|
||||
trianglePath.lineTo(bottomLeftPoint);
|
||||
trianglePath.lineTo(bottomRightPoint);
|
||||
trianglePath.lineTo(topPoint);
|
||||
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.fillPath(trianglePath, QBrush(QColor("#555555")));
|
||||
}
|
||||
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -14,8 +14,10 @@
|
||||
#include <GemCatalog/GemModel.h>
|
||||
#include <GemCatalog/GemSortFilterProxyModel.h>
|
||||
#include <TagWidget.h>
|
||||
#include <QFrame>
|
||||
#include <DownloadController.h>
|
||||
|
||||
#include <QFrame>
|
||||
#include <QScrollArea>
|
||||
#endif
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QPushButton)
|
||||
@@ -24,16 +26,23 @@ QT_FORWARD_DECLARE_CLASS(QVBoxLayout)
|
||||
QT_FORWARD_DECLARE_CLASS(QHBoxLayout)
|
||||
QT_FORWARD_DECLARE_CLASS(QHideEvent)
|
||||
QT_FORWARD_DECLARE_CLASS(QMoveEvent)
|
||||
QT_FORWARD_DECLARE_CLASS(QMovie)
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
class CartOverlayWidget
|
||||
: public QWidget
|
||||
class GemCartWidget
|
||||
: public QScrollArea
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
|
||||
public:
|
||||
CartOverlayWidget(GemModel* gemModel, DownloadController* downloadController, QWidget* parent = nullptr);
|
||||
GemCartWidget(GemModel* gemModel, DownloadController* downloadController, QWidget* parent = nullptr);
|
||||
~GemCartWidget();
|
||||
|
||||
public slots:
|
||||
void GemDownloadAdded(const QString& gemName);
|
||||
void GemDownloadRemoved(const QString& gemName);
|
||||
void GemDownloadProgress(const QString& gemName, int bytesDownloaded, int totalBytes);
|
||||
|
||||
private:
|
||||
QVector<Tag> GetTagsFromModelIndices(const QVector<QModelIndex>& gems) const;
|
||||
@@ -47,6 +56,9 @@ namespace O3DE::ProjectManager
|
||||
GemModel* m_gemModel = nullptr;
|
||||
DownloadController* m_downloadController = nullptr;
|
||||
|
||||
QWidget* m_downloadSectionWidget = nullptr;
|
||||
QWidget* m_downloadingListWidget = nullptr;
|
||||
|
||||
inline constexpr static int s_width = 240;
|
||||
};
|
||||
|
||||
@@ -58,7 +70,10 @@ namespace O3DE::ProjectManager
|
||||
public:
|
||||
CartButton(GemModel* gemModel, DownloadController* downloadController, QWidget* parent = nullptr);
|
||||
~CartButton();
|
||||
void ShowOverlay();
|
||||
void ShowGemCart();
|
||||
|
||||
signals:
|
||||
void UpdateGemCart(QWidget* gemCart);
|
||||
|
||||
private:
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
@@ -68,7 +83,7 @@ namespace O3DE::ProjectManager
|
||||
QHBoxLayout* m_layout = nullptr;
|
||||
QLabel* m_countLabel = nullptr;
|
||||
QPushButton* m_dropDownButton = nullptr;
|
||||
CartOverlayWidget* m_cartOverlay = nullptr;
|
||||
GemCartWidget* m_gemCart = nullptr;
|
||||
DownloadController* m_downloadController = nullptr;
|
||||
|
||||
inline constexpr static int s_iconSize = 24;
|
||||
@@ -86,12 +101,28 @@ namespace O3DE::ProjectManager
|
||||
|
||||
void ReinitForProject();
|
||||
|
||||
public slots:
|
||||
void GemDownloadAdded(const QString& gemName);
|
||||
void GemDownloadRemoved(const QString& gemName);
|
||||
void GemCartShown(bool state = false);
|
||||
|
||||
signals:
|
||||
void AddGem();
|
||||
void OpenGemsRepo();
|
||||
void RefreshGems();
|
||||
void UpdateGemCart(QWidget* gemCart);
|
||||
|
||||
protected slots:
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
|
||||
private:
|
||||
AzQtComponents::SearchLineEdit* m_filterLineEdit = nullptr;
|
||||
inline constexpr static int s_height = 60;
|
||||
DownloadController* m_downloadController = nullptr;
|
||||
QLabel* m_downloadSpinner = nullptr;
|
||||
QLabel* m_downloadLabel = nullptr;
|
||||
QMovie* m_downloadSpinnerMovie = nullptr;
|
||||
CartButton* m_cartButton = nullptr;
|
||||
bool m_showGemCart = false;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -8,11 +8,20 @@
|
||||
|
||||
#include <GemCatalog/GemCatalogScreen.h>
|
||||
#include <PythonBindingsInterface.h>
|
||||
#include <GemCatalog/GemCatalogHeaderWidget.h>
|
||||
#include <GemCatalog/GemFilterWidget.h>
|
||||
#include <GemCatalog/GemListView.h>
|
||||
#include <GemCatalog/GemInspector.h>
|
||||
#include <GemCatalog/GemModel.h>
|
||||
#include <GemCatalog/GemListHeaderWidget.h>
|
||||
#include <GemCatalog/GemSortFilterProxyModel.h>
|
||||
#include <GemCatalog/GemRequirementDialog.h>
|
||||
#include <GemCatalog/GemDependenciesDialog.h>
|
||||
#include <GemCatalog/GemUpdateDialog.h>
|
||||
#include <GemCatalog/GemUninstallDialog.h>
|
||||
#include <DownloadController.h>
|
||||
#include <ProjectUtils.h>
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
@@ -24,6 +33,7 @@
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QHash>
|
||||
#include <QStackedWidget>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -47,8 +57,12 @@ namespace O3DE::ProjectManager
|
||||
vLayout->addWidget(m_headerWidget);
|
||||
|
||||
connect(m_gemModel, &GemModel::gemStatusChanged, this, &GemCatalogScreen::OnGemStatusChanged);
|
||||
connect(m_gemModel, &GemModel::dependencyGemStatusChanged, this, &GemCatalogScreen::OnDependencyGemStatusChanged);
|
||||
connect(m_gemModel->GetSelectionModel(), &QItemSelectionModel::selectionChanged, this, [this]{ ShowInspector(); });
|
||||
connect(m_headerWidget, &GemCatalogHeaderWidget::RefreshGems, this, &GemCatalogScreen::Refresh);
|
||||
connect(m_headerWidget, &GemCatalogHeaderWidget::OpenGemsRepo, this, &GemCatalogScreen::HandleOpenGemRepo);
|
||||
connect(m_headerWidget, &GemCatalogHeaderWidget::AddGem, this, &GemCatalogScreen::OnAddGemClicked);
|
||||
connect(m_headerWidget, &GemCatalogHeaderWidget::UpdateGemCart, this, &GemCatalogScreen::UpdateAndShowGemCart);
|
||||
connect(m_downloadController, &DownloadController::Done, this, &GemCatalogScreen::OnGemDownloadResult);
|
||||
|
||||
QHBoxLayout* hLayout = new QHBoxLayout();
|
||||
@@ -56,10 +70,15 @@ namespace O3DE::ProjectManager
|
||||
vLayout->addLayout(hLayout);
|
||||
|
||||
m_gemListView = new GemListView(m_proxyModel, m_proxyModel->GetSelectionModel(), this);
|
||||
|
||||
m_rightPanelStack = new QStackedWidget(this);
|
||||
m_rightPanelStack->setFixedWidth(240);
|
||||
|
||||
m_gemInspector = new GemInspector(m_gemModel, this);
|
||||
m_gemInspector->setFixedWidth(240);
|
||||
|
||||
connect(m_gemInspector, &GemInspector::TagClicked, [=](const Tag& tag) { SelectGem(tag.id); });
|
||||
connect(m_gemInspector, &GemInspector::UpdateGem, this, &GemCatalogScreen::UpdateGem);
|
||||
connect(m_gemInspector, &GemInspector::UninstallGem, this, &GemCatalogScreen::UninstallGem);
|
||||
|
||||
QWidget* filterWidget = new QWidget(this);
|
||||
filterWidget->setFixedWidth(240);
|
||||
@@ -78,7 +97,9 @@ namespace O3DE::ProjectManager
|
||||
|
||||
hLayout->addWidget(filterWidget);
|
||||
hLayout->addLayout(middleVLayout);
|
||||
hLayout->addWidget(m_gemInspector);
|
||||
|
||||
hLayout->addWidget(m_rightPanelStack);
|
||||
m_rightPanelStack->addWidget(m_gemInspector);
|
||||
|
||||
m_notificationsView = AZStd::make_unique<AzToolsFramework::ToastNotificationsView>(this, AZ_CRC("GemCatalogNotificationsView"));
|
||||
m_notificationsView->SetOffset(QPoint(10, 70));
|
||||
@@ -90,9 +111,16 @@ namespace O3DE::ProjectManager
|
||||
m_projectPath = projectPath;
|
||||
m_gemModel->Clear();
|
||||
m_gemsToRegisterWithProject.clear();
|
||||
|
||||
if (m_filterWidget)
|
||||
{
|
||||
// disconnect so we don't update the status filter for every gem we add
|
||||
disconnect(m_gemModel, &GemModel::dataChanged, m_filterWidget, &GemFilterWidget::ResetGemStatusFilter);
|
||||
}
|
||||
|
||||
FillModel(projectPath);
|
||||
|
||||
m_proxyModel->ResetFilters();
|
||||
m_proxyModel->ResetFilters(false);
|
||||
m_proxyModel->sort(/*column=*/0);
|
||||
|
||||
if (m_filterWidget)
|
||||
@@ -111,9 +139,10 @@ namespace O3DE::ProjectManager
|
||||
|
||||
// Select the first entry after everything got correctly sized
|
||||
QTimer::singleShot(200, [=]{
|
||||
QModelIndex firstModelIndex = m_gemListView->model()->index(0,0);
|
||||
m_gemListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect);
|
||||
});
|
||||
QModelIndex firstModelIndex = m_gemModel->index(0, 0);
|
||||
QModelIndex proxyIndex = m_proxyModel->mapFromSource(firstModelIndex);
|
||||
m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect);
|
||||
});
|
||||
}
|
||||
|
||||
void GemCatalogScreen::OnAddGemClicked()
|
||||
@@ -173,7 +202,7 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
|
||||
// add all the gem repos into the hash
|
||||
const AZ::Outcome<QVector<GemInfo>, AZStd::string>& allRepoGemInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoGemsInfos();
|
||||
const AZ::Outcome<QVector<GemInfo>, AZStd::string>& allRepoGemInfosResult = PythonBindingsInterface::Get()->GetGemInfosForAllRepos();
|
||||
if (allRepoGemInfosResult.IsSuccess())
|
||||
{
|
||||
const QVector<GemInfo>& allRepoGemInfos = allRepoGemInfosResult.GetValue();
|
||||
@@ -195,7 +224,7 @@ namespace O3DE::ProjectManager
|
||||
const bool gemFound = gemInfoHash.contains(gemName);
|
||||
if (!gemFound && !m_gemModel->IsAdded(index) && !m_gemModel->IsAddedDependency(index))
|
||||
{
|
||||
m_gemModel->removeRow(i);
|
||||
m_gemModel->RemoveGem(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -221,8 +250,11 @@ namespace O3DE::ProjectManager
|
||||
m_proxyModel->sort(/*column=*/0);
|
||||
|
||||
// temporary, until we can refresh filter counts
|
||||
m_proxyModel->ResetFilters();
|
||||
m_proxyModel->ResetFilters(false);
|
||||
m_filterWidget->ResetAllFilters();
|
||||
|
||||
// Reselect the same selection to proc UI updates
|
||||
m_proxyModel->GetSelectionModel()->setCurrentIndex(m_proxyModel->GetSelectionModel()->currentIndex(), QItemSelectionModel::Select);
|
||||
}
|
||||
|
||||
void GemCatalogScreen::OnGemStatusChanged(const QString& gemName, uint32_t numChangedDependencies)
|
||||
@@ -246,23 +278,25 @@ namespace O3DE::ProjectManager
|
||||
notification = GemModel::GetDisplayName(modelIndex);
|
||||
if (numChangedDependencies > 0)
|
||||
{
|
||||
notification += " " + tr("and") + " ";
|
||||
notification += tr(" and ");
|
||||
}
|
||||
if (added && GemModel::GetDownloadStatus(modelIndex) == GemInfo::DownloadStatus::NotDownloaded)
|
||||
if (added && (GemModel::GetDownloadStatus(modelIndex) == GemInfo::DownloadStatus::NotDownloaded) ||
|
||||
(GemModel::GetDownloadStatus(modelIndex) == GemInfo::DownloadStatus::DownloadFailed))
|
||||
{
|
||||
m_downloadController->AddGemDownload(GemModel::GetName(modelIndex));
|
||||
GemModel::SetDownloadStatus(*m_gemModel, modelIndex, GemInfo::DownloadStatus::Downloading);
|
||||
}
|
||||
}
|
||||
|
||||
if (numChangedDependencies == 1 )
|
||||
if (numChangedDependencies == 1)
|
||||
{
|
||||
notification += "1 Gem " + tr("dependency");
|
||||
notification += tr("1 Gem dependency");
|
||||
}
|
||||
else if (numChangedDependencies > 1)
|
||||
{
|
||||
notification += QString("%1 Gem ").arg(numChangedDependencies) + tr("dependencies");
|
||||
notification += tr("%1 Gem %2").arg(QString(numChangedDependencies), tr("dependencies"));
|
||||
}
|
||||
notification += " " + (added ? tr("activated") : tr("deactivated"));
|
||||
notification += (added ? tr(" activated") : tr(" deactivated"));
|
||||
|
||||
AzQtComponents::ToastConfiguration toastConfiguration(AzQtComponents::ToastType::Custom, notification, "");
|
||||
toastConfiguration.m_customIconImage = ":/gem.svg";
|
||||
@@ -272,6 +306,18 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
void GemCatalogScreen::OnDependencyGemStatusChanged(const QString& gemName)
|
||||
{
|
||||
QModelIndex modelIndex = m_gemModel->FindIndexByNameString(gemName);
|
||||
bool added = GemModel::IsAddedDependency(modelIndex);
|
||||
if (added && (GemModel::GetDownloadStatus(modelIndex) == GemInfo::DownloadStatus::NotDownloaded) ||
|
||||
(GemModel::GetDownloadStatus(modelIndex) == GemInfo::DownloadStatus::DownloadFailed))
|
||||
{
|
||||
m_downloadController->AddGemDownload(GemModel::GetName(modelIndex));
|
||||
GemModel::SetDownloadStatus(*m_gemModel, modelIndex, GemInfo::DownloadStatus::Downloading);
|
||||
}
|
||||
}
|
||||
|
||||
void GemCatalogScreen::SelectGem(const QString& gemName)
|
||||
{
|
||||
QModelIndex modelIndex = m_gemModel->FindIndexByNameString(gemName);
|
||||
@@ -282,8 +328,108 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
|
||||
QModelIndex proxyIndex = m_proxyModel->mapFromSource(modelIndex);
|
||||
m_proxyModel->GetSelectionModel()->select(proxyIndex, QItemSelectionModel::ClearAndSelect);
|
||||
m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect);
|
||||
m_gemListView->scrollTo(proxyIndex);
|
||||
|
||||
ShowInspector();
|
||||
}
|
||||
|
||||
void GemCatalogScreen::UpdateGem(const QModelIndex& modelIndex)
|
||||
{
|
||||
const QString selectedGemName = m_gemModel->GetName(modelIndex);
|
||||
const QString selectedGemLastUpdate = m_gemModel->GetLastUpdated(modelIndex);
|
||||
const QString selectedDisplayGemName = m_gemModel->GetDisplayName(modelIndex);
|
||||
const QString selectedGemRepoUri = m_gemModel->GetRepoUri(modelIndex);
|
||||
|
||||
// Refresh gem repo
|
||||
if (!selectedGemRepoUri.isEmpty())
|
||||
{
|
||||
AZ::Outcome<void, AZStd::string> refreshResult = PythonBindingsInterface::Get()->RefreshGemRepo(selectedGemRepoUri);
|
||||
if (refreshResult.IsSuccess())
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(
|
||||
this, tr("Operation failed"),
|
||||
tr("Failed to refresh gem repository %1<br>Error:<br>%2").arg(selectedGemRepoUri, refreshResult.GetError().c_str()));
|
||||
}
|
||||
}
|
||||
// If repo uri isn't specified warn user that repo might not be refreshed
|
||||
else
|
||||
{
|
||||
int result = QMessageBox::warning(
|
||||
this, tr("Gem Repository Unspecified"),
|
||||
tr("The repo for %1 is unspecfied. Repository cannot be automatically refreshed. "
|
||||
"Please ensure this gem's repo is refreshed before attempting to update.")
|
||||
.arg(selectedDisplayGemName),
|
||||
QMessageBox::Cancel, QMessageBox::Ok);
|
||||
|
||||
// Allow user to cancel update to manually refresh repo
|
||||
if (result != QMessageBox::Ok)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there is an update avaliable now that repo is refreshed
|
||||
bool updateAvaliable = PythonBindingsInterface::Get()->IsGemUpdateAvaliable(selectedGemName, selectedGemLastUpdate);
|
||||
|
||||
GemUpdateDialog* confirmUpdateDialog = new GemUpdateDialog(selectedGemName, updateAvaliable, this);
|
||||
if (confirmUpdateDialog->exec() == QDialog::Accepted)
|
||||
{
|
||||
m_downloadController->AddGemDownload(selectedGemName);
|
||||
}
|
||||
}
|
||||
|
||||
void GemCatalogScreen::UninstallGem(const QModelIndex& modelIndex)
|
||||
{
|
||||
const QString selectedDisplayGemName = m_gemModel->GetDisplayName(modelIndex);
|
||||
|
||||
GemUninstallDialog* confirmUninstallDialog = new GemUninstallDialog(selectedDisplayGemName, this);
|
||||
if (confirmUninstallDialog->exec() == QDialog::Accepted)
|
||||
{
|
||||
const QString selectedGemPath = m_gemModel->GetPath(modelIndex);
|
||||
|
||||
const bool wasAdded = GemModel::WasPreviouslyAdded(modelIndex);
|
||||
const bool wasAddedDependency = GemModel::WasPreviouslyAddedDependency(modelIndex);
|
||||
|
||||
// Remove gem from gems to be added to update any dependencies
|
||||
GemModel::SetIsAdded(*m_gemModel, modelIndex, false);
|
||||
GemModel::DeactivateDependentGems(*m_gemModel, modelIndex);
|
||||
|
||||
// Unregister the gem
|
||||
auto unregisterResult = PythonBindingsInterface::Get()->UnregisterGem(selectedGemPath);
|
||||
if (!unregisterResult)
|
||||
{
|
||||
QMessageBox::critical(this, tr("Failed to unregister gem"), unregisterResult.GetError().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
const QString selectedGemName = m_gemModel->GetName(modelIndex);
|
||||
|
||||
// Remove gem from model
|
||||
m_gemModel->RemoveGem(modelIndex);
|
||||
|
||||
// Delete uninstalled gem directory
|
||||
if (!ProjectUtils::DeleteProjectFiles(selectedGemPath, /*force*/true))
|
||||
{
|
||||
QMessageBox::critical(
|
||||
this, tr("Failed to remove gem directory"), tr("Could not delete gem directory at:<br>%1").arg(selectedGemPath));
|
||||
}
|
||||
|
||||
// Show undownloaded remote gem again
|
||||
Refresh();
|
||||
|
||||
// Select remote gem
|
||||
QModelIndex remoteGemIndex = m_gemModel->FindIndexByNameString(selectedGemName);
|
||||
GemModel::SetWasPreviouslyAdded(*m_gemModel, remoteGemIndex, wasAdded);
|
||||
GemModel::SetWasPreviouslyAddedDependency(*m_gemModel, remoteGemIndex, wasAddedDependency);
|
||||
QModelIndex proxyIndex = m_proxyModel->mapFromSource(remoteGemIndex);
|
||||
m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GemCatalogScreen::hideEvent(QHideEvent* event)
|
||||
@@ -324,7 +470,7 @@ namespace O3DE::ProjectManager
|
||||
m_gemModel->AddGem(gemInfo);
|
||||
}
|
||||
|
||||
const AZ::Outcome<QVector<GemInfo>, AZStd::string>& allRepoGemInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoGemsInfos();
|
||||
const AZ::Outcome<QVector<GemInfo>, AZStd::string>& allRepoGemInfosResult = PythonBindingsInterface::Get()->GetGemInfosForAllRepos();
|
||||
if (allRepoGemInfosResult.IsSuccess())
|
||||
{
|
||||
const QVector<GemInfo>& allRepoGemInfos = allRepoGemInfosResult.GetValue();
|
||||
@@ -380,6 +526,12 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
void GemCatalogScreen::ShowInspector()
|
||||
{
|
||||
m_rightPanelStack->setCurrentIndex(RightPanelWidgetOrder::Inspector);
|
||||
m_headerWidget->GemCartShown();
|
||||
}
|
||||
|
||||
GemCatalogScreen::EnableDisableGemsResult GemCatalogScreen::EnableDisableGemsForProject(const QString& projectPath)
|
||||
{
|
||||
IPythonBindings* pythonBindings = PythonBindingsInterface::Get();
|
||||
@@ -412,7 +564,9 @@ namespace O3DE::ProjectManager
|
||||
const QString& gemPath = GemModel::GetPath(modelIndex);
|
||||
|
||||
// make sure any remote gems we added were downloaded successfully
|
||||
if (GemModel::GetGemOrigin(modelIndex) == GemInfo::Remote && GemModel::GetDownloadStatus(modelIndex) != GemInfo::Downloaded)
|
||||
const GemInfo::DownloadStatus status = GemModel::GetDownloadStatus(modelIndex);
|
||||
if (GemModel::GetGemOrigin(modelIndex) == GemInfo::Remote &&
|
||||
!(status == GemInfo::Downloaded || status == GemInfo::DownloadSuccessful))
|
||||
{
|
||||
QMessageBox::critical(
|
||||
nullptr, "Cannot add gem that isn't downloaded",
|
||||
@@ -459,12 +613,25 @@ namespace O3DE::ProjectManager
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::GemRepos);
|
||||
}
|
||||
|
||||
void GemCatalogScreen::UpdateAndShowGemCart(QWidget* cartWidget)
|
||||
{
|
||||
QWidget* previousCart = m_rightPanelStack->widget(RightPanelWidgetOrder::Cart);
|
||||
if (previousCart)
|
||||
{
|
||||
m_rightPanelStack->removeWidget(previousCart);
|
||||
}
|
||||
|
||||
m_rightPanelStack->insertWidget(RightPanelWidgetOrder::Cart, cartWidget);
|
||||
m_rightPanelStack->setCurrentIndex(RightPanelWidgetOrder::Cart);
|
||||
}
|
||||
|
||||
void GemCatalogScreen::OnGemDownloadResult(const QString& gemName, bool succeeded)
|
||||
{
|
||||
if (succeeded)
|
||||
{
|
||||
// refresh the information for downloaded gems
|
||||
const AZ::Outcome<QVector<GemInfo>, AZStd::string>& allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(m_projectPath);
|
||||
const AZ::Outcome<QVector<GemInfo>, AZStd::string>& allGemInfosResult =
|
||||
PythonBindingsInterface::Get()->GetAllGemInfos(m_projectPath);
|
||||
if (allGemInfosResult.IsSuccess())
|
||||
{
|
||||
// we should find the gem name now in all gem infos
|
||||
@@ -472,19 +639,47 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
if (gemInfo.m_name == gemName)
|
||||
{
|
||||
QModelIndex index = m_gemModel->FindIndexByNameString(gemName);
|
||||
if (index.isValid())
|
||||
QModelIndex oldIndex = m_gemModel->FindIndexByNameString(gemName);
|
||||
if (oldIndex.isValid())
|
||||
{
|
||||
m_gemModel->setData(index, GemInfo::Downloaded, GemModel::RoleDownloadStatus);
|
||||
m_gemModel->setData(index, gemInfo.m_path, GemModel::RolePath);
|
||||
m_gemModel->setData(index, gemInfo.m_path, GemModel::RoleDirectoryLink);
|
||||
// Check if old gem is selected
|
||||
bool oldGemSelected = false;
|
||||
if (m_gemModel->GetSelectionModel()->currentIndex() == oldIndex)
|
||||
{
|
||||
oldGemSelected = true;
|
||||
}
|
||||
|
||||
// Remove old remote gem
|
||||
m_gemModel->RemoveGem(oldIndex);
|
||||
|
||||
// Add new downloaded version of gem
|
||||
QModelIndex newIndex = m_gemModel->AddGem(gemInfo);
|
||||
GemModel::SetDownloadStatus(*m_gemModel, newIndex, GemInfo::DownloadSuccessful);
|
||||
GemModel::SetIsAdded(*m_gemModel, newIndex, true);
|
||||
|
||||
// Select new version of gem if it was previously selected
|
||||
if (oldGemSelected)
|
||||
{
|
||||
QModelIndex proxyIndex = m_proxyModel->mapFromSource(newIndex);
|
||||
m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QModelIndex index = m_gemModel->FindIndexByNameString(gemName);
|
||||
if (index.isValid())
|
||||
{
|
||||
GemModel::SetIsAdded(*m_gemModel, index, false);
|
||||
GemModel::DeactivateDependentGems(*m_gemModel, index);
|
||||
GemModel::SetDownloadStatus(*m_gemModel, index, GemInfo::DownloadFailed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProjectManagerScreen GemCatalogScreen::GetScreenEnum()
|
||||
|
||||
@@ -12,18 +12,24 @@
|
||||
#include <ScreenWidget.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
#include <AzToolsFramework/UI/Notifications/ToastNotificationsView.h>
|
||||
#include <GemCatalog/GemCatalogHeaderWidget.h>
|
||||
#include <GemCatalog/GemFilterWidget.h>
|
||||
#include <GemCatalog/GemListView.h>
|
||||
#include <GemCatalog/GemInspector.h>
|
||||
#include <GemCatalog/GemModel.h>
|
||||
#include <GemCatalog/GemSortFilterProxyModel.h>
|
||||
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
#endif
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QVBoxLayout)
|
||||
QT_FORWARD_DECLARE_CLASS(QStackedWidget)
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
QT_FORWARD_DECLARE_CLASS(GemCatalogHeaderWidget)
|
||||
QT_FORWARD_DECLARE_CLASS(GemFilterWidget)
|
||||
QT_FORWARD_DECLARE_CLASS(GemListView)
|
||||
QT_FORWARD_DECLARE_CLASS(GemInspector)
|
||||
QT_FORWARD_DECLARE_CLASS(GemModel)
|
||||
QT_FORWARD_DECLARE_CLASS(GemSortFilterProxyModel)
|
||||
QT_FORWARD_DECLARE_CLASS(DownloadController)
|
||||
|
||||
class GemCatalogScreen
|
||||
: public ScreenWidget
|
||||
{
|
||||
@@ -47,10 +53,13 @@ namespace O3DE::ProjectManager
|
||||
|
||||
public slots:
|
||||
void OnGemStatusChanged(const QString& gemName, uint32_t numChangedDependencies);
|
||||
void OnDependencyGemStatusChanged(const QString& gemName);
|
||||
void OnAddGemClicked();
|
||||
void SelectGem(const QString& gemName);
|
||||
void OnGemDownloadResult(const QString& gemName, bool succeeded = true);
|
||||
void Refresh();
|
||||
void UpdateGem(const QModelIndex& modelIndex);
|
||||
void UninstallGem(const QModelIndex& modelIndex);
|
||||
|
||||
protected:
|
||||
void hideEvent(QHideEvent* event) override;
|
||||
@@ -60,14 +69,22 @@ namespace O3DE::ProjectManager
|
||||
|
||||
private slots:
|
||||
void HandleOpenGemRepo();
|
||||
|
||||
void UpdateAndShowGemCart(QWidget* cartWidget);
|
||||
void ShowInspector();
|
||||
|
||||
private:
|
||||
enum RightPanelWidgetOrder
|
||||
{
|
||||
Inspector = 0,
|
||||
Cart
|
||||
};
|
||||
|
||||
void FillModel(const QString& projectPath);
|
||||
|
||||
AZStd::unique_ptr<AzToolsFramework::ToastNotificationsView> m_notificationsView;
|
||||
|
||||
GemListView* m_gemListView = nullptr;
|
||||
QStackedWidget* m_rightPanelStack = nullptr;
|
||||
GemInspector* m_gemInspector = nullptr;
|
||||
GemModel* m_gemModel = nullptr;
|
||||
GemCatalogHeaderWidget* m_headerWidget = nullptr;
|
||||
@@ -77,6 +94,6 @@ namespace O3DE::ProjectManager
|
||||
DownloadController* m_downloadController = nullptr;
|
||||
bool m_notificationsEnabled = true;
|
||||
QSet<QString> m_gemsToRegisterWithProject;
|
||||
QString m_projectPath = nullptr;
|
||||
QString m_projectPath;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -221,7 +221,6 @@ namespace O3DE::ProjectManager
|
||||
ResetGemStatusFilter();
|
||||
ResetGemOriginFilter();
|
||||
ResetTypeFilter();
|
||||
ResetPlatformFilter();
|
||||
ResetFeatureFilter();
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,9 @@ namespace O3DE::ProjectManager
|
||||
UnknownDownloadStatus = -1,
|
||||
NotDownloaded,
|
||||
Downloading,
|
||||
Downloaded,
|
||||
DownloadSuccessful,
|
||||
DownloadFailed,
|
||||
Downloaded
|
||||
};
|
||||
static QString GetDownloadStatusString(DownloadStatus status);
|
||||
|
||||
@@ -85,6 +87,7 @@ namespace O3DE::ProjectManager
|
||||
QString m_licenseLink;
|
||||
QString m_directoryLink;
|
||||
QString m_documentationLink;
|
||||
QString m_repoUri;
|
||||
QString m_version = "Unknown Version";
|
||||
QString m_lastUpdatedDate = "Unknown Date";
|
||||
int m_binarySizeInKB = 0;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <QSpacerItem>
|
||||
#include <QVBoxLayout>
|
||||
#include <QIcon>
|
||||
#include <QPushButton>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -52,10 +53,13 @@ namespace O3DE::ProjectManager
|
||||
Update(selectedIndices[0]);
|
||||
}
|
||||
|
||||
void SetLabelElidedText(QLabel* label, QString text)
|
||||
void SetLabelElidedText(QLabel* label, QString text, int labelWidth = 0)
|
||||
{
|
||||
QFontMetrics nameFontMetrics(label->font());
|
||||
int labelWidth = label->width();
|
||||
if (!labelWidth)
|
||||
{
|
||||
labelWidth = label->width();
|
||||
}
|
||||
|
||||
// Don't elide if the widgets are sized too small (sometimes occurs when loading gem catalog)
|
||||
if (labelWidth > 100)
|
||||
@@ -70,6 +74,8 @@ namespace O3DE::ProjectManager
|
||||
|
||||
void GemInspector::Update(const QModelIndex& modelIndex)
|
||||
{
|
||||
m_curModelIndex = modelIndex;
|
||||
|
||||
if (!modelIndex.isValid())
|
||||
{
|
||||
m_mainWidget->hide();
|
||||
@@ -81,7 +87,8 @@ namespace O3DE::ProjectManager
|
||||
m_summaryLabel->setText(m_model->GetSummary(modelIndex));
|
||||
m_summaryLabel->adjustSize();
|
||||
|
||||
m_licenseLinkLabel->setText(m_model->GetLicenseText(modelIndex));
|
||||
// Manually define remaining space to elide text because spacer would like to take all of the space
|
||||
SetLabelElidedText(m_licenseLinkLabel, m_model->GetLicenseText(modelIndex), width() - m_licenseLabel->width() - 35);
|
||||
m_licenseLinkLabel->SetUrl(m_model->GetLicenseLink(modelIndex));
|
||||
|
||||
m_directoryLinkLabel->SetUrl(m_model->GetDirectoryLink(modelIndex));
|
||||
@@ -123,6 +130,20 @@ namespace O3DE::ProjectManager
|
||||
const int binarySize = m_model->GetBinarySizeInKB(modelIndex);
|
||||
m_binarySizeLabel->setText(tr("Binary Size: %1").arg(binarySize ? tr("%1 KB").arg(binarySize) : tr("Unknown")));
|
||||
|
||||
// Update and Uninstall buttons
|
||||
if (m_model->GetGemOrigin(modelIndex) == GemInfo::Remote &&
|
||||
(m_model->GetDownloadStatus(modelIndex) == GemInfo::Downloaded ||
|
||||
m_model->GetDownloadStatus(modelIndex) == GemInfo::DownloadSuccessful))
|
||||
{
|
||||
m_updateGemButton->show();
|
||||
m_uninstallGemButton->show();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_updateGemButton->hide();
|
||||
m_uninstallGemButton->hide();
|
||||
}
|
||||
|
||||
m_mainWidget->adjustSize();
|
||||
m_mainWidget->show();
|
||||
}
|
||||
@@ -158,8 +179,8 @@ namespace O3DE::ProjectManager
|
||||
licenseHLayout->setAlignment(Qt::AlignLeft);
|
||||
m_mainLayout->addLayout(licenseHLayout);
|
||||
|
||||
QLabel* licenseLabel = CreateStyledLabel(licenseHLayout, s_baseFontSize, s_headerColor);
|
||||
licenseLabel->setText(tr("License: "));
|
||||
m_licenseLabel = CreateStyledLabel(licenseHLayout, s_baseFontSize, s_headerColor);
|
||||
m_licenseLabel->setText(tr("License: "));
|
||||
|
||||
m_licenseLinkLabel = new LinkLabel("", QUrl(), s_baseFontSize);
|
||||
licenseHLayout->addWidget(m_licenseLinkLabel);
|
||||
@@ -223,7 +244,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
// Depending gems
|
||||
m_dependingGems = new GemsSubWidget();
|
||||
connect(m_dependingGems, &GemsSubWidget::TagClicked, this, [=](const Tag& tag){ emit TagClicked(tag); });
|
||||
connect(m_dependingGems, &GemsSubWidget::TagClicked, this, [this](const Tag& tag){ emit TagClicked(tag); });
|
||||
m_mainLayout->addWidget(m_dependingGems);
|
||||
m_mainLayout->addSpacing(20);
|
||||
|
||||
@@ -234,5 +255,20 @@ namespace O3DE::ProjectManager
|
||||
m_versionLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor);
|
||||
m_lastUpdatedLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor);
|
||||
m_binarySizeLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor);
|
||||
|
||||
m_mainLayout->addSpacing(20);
|
||||
|
||||
// Update and Uninstall buttons
|
||||
m_updateGemButton = new QPushButton(tr("Update Gem"));
|
||||
m_updateGemButton->setObjectName("gemCatalogUpdateGemButton");
|
||||
m_mainLayout->addWidget(m_updateGemButton);
|
||||
connect(m_updateGemButton, &QPushButton::clicked, this , [this]{ emit UpdateGem(m_curModelIndex); });
|
||||
|
||||
m_mainLayout->addSpacing(10);
|
||||
|
||||
m_uninstallGemButton = new QPushButton(tr("Uninstall Gem"));
|
||||
m_uninstallGemButton->setObjectName("gemCatalogUninstallGemButton");
|
||||
m_mainLayout->addWidget(m_uninstallGemButton);
|
||||
connect(m_uninstallGemButton, &QPushButton::clicked, this , [this]{ emit UninstallGem(m_curModelIndex); });
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
|
||||
#include <QItemSelection>
|
||||
#include <QScrollArea>
|
||||
#include <QSpacerItem>
|
||||
#endif
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QVBoxLayout)
|
||||
QT_FORWARD_DECLARE_CLASS(QLabel)
|
||||
QT_FORWARD_DECLARE_CLASS(QSpacerItem)
|
||||
QT_FORWARD_DECLARE_CLASS(QPushButton)
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -45,6 +46,8 @@ namespace O3DE::ProjectManager
|
||||
|
||||
signals:
|
||||
void TagClicked(const Tag& tag);
|
||||
void UpdateGem(const QModelIndex& modelIndex);
|
||||
void UninstallGem(const QModelIndex& modelIndex);
|
||||
|
||||
private slots:
|
||||
void OnSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
|
||||
@@ -55,11 +58,13 @@ namespace O3DE::ProjectManager
|
||||
GemModel* m_model = nullptr;
|
||||
QWidget* m_mainWidget = nullptr;
|
||||
QVBoxLayout* m_mainLayout = nullptr;
|
||||
QModelIndex m_curModelIndex;
|
||||
|
||||
// General info (top) section
|
||||
QLabel* m_nameLabel = nullptr;
|
||||
QLabel* m_creatorLabel = nullptr;
|
||||
QLabel* m_summaryLabel = nullptr;
|
||||
QLabel* m_licenseLabel = nullptr;
|
||||
LinkLabel* m_licenseLinkLabel = nullptr;
|
||||
LinkLabel* m_directoryLinkLabel = nullptr;
|
||||
LinkLabel* m_documentationLinkLabel = nullptr;
|
||||
@@ -77,5 +82,8 @@ namespace O3DE::ProjectManager
|
||||
QLabel* m_versionLabel = nullptr;
|
||||
QLabel* m_lastUpdatedLabel = nullptr;
|
||||
QLabel* m_binarySizeLabel = nullptr;
|
||||
|
||||
QPushButton* m_updateGemButton = nullptr;
|
||||
QPushButton* m_uninstallGemButton = nullptr;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -37,6 +37,8 @@ namespace O3DE::ProjectManager
|
||||
|
||||
SetStatusIcon(m_notDownloadedPixmap, ":/Download.svg");
|
||||
SetStatusIcon(m_unknownStatusPixmap, ":/X.svg");
|
||||
SetStatusIcon(m_downloadSuccessfulPixmap, ":/checkmark.svg");
|
||||
SetStatusIcon(m_downloadFailedPixmap, ":/Warning.svg");
|
||||
|
||||
m_downloadingMovie = new QMovie(":/in_progress.gif");
|
||||
}
|
||||
@@ -480,6 +482,14 @@ namespace O3DE::ProjectManager
|
||||
currentFrame = currentFrame.scaled(s_statusIconSize, s_statusIconSize);
|
||||
statusPixmap = ¤tFrame;
|
||||
}
|
||||
else if (downloadStatus == GemInfo::DownloadStatus::DownloadSuccessful)
|
||||
{
|
||||
statusPixmap = &m_downloadSuccessfulPixmap;
|
||||
}
|
||||
else if (downloadStatus == GemInfo::DownloadStatus::DownloadFailed)
|
||||
{
|
||||
statusPixmap = &m_downloadFailedPixmap;
|
||||
}
|
||||
else if (downloadStatus == GemInfo::DownloadStatus::NotDownloaded)
|
||||
{
|
||||
statusPixmap = &m_notDownloadedPixmap;
|
||||
|
||||
@@ -97,6 +97,8 @@ namespace O3DE::ProjectManager
|
||||
|
||||
QPixmap m_unknownStatusPixmap;
|
||||
QPixmap m_notDownloadedPixmap;
|
||||
QPixmap m_downloadSuccessfulPixmap;
|
||||
QPixmap m_downloadFailedPixmap;
|
||||
QMovie* m_downloadingMovie = nullptr;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -26,14 +26,14 @@ namespace O3DE::ProjectManager
|
||||
return m_selectionModel;
|
||||
}
|
||||
|
||||
void GemModel::AddGem(const GemInfo& gemInfo)
|
||||
QModelIndex GemModel::AddGem(const GemInfo& gemInfo)
|
||||
{
|
||||
if (FindIndexByNameString(gemInfo.m_name).isValid())
|
||||
{
|
||||
// do not add gems with duplicate names
|
||||
// this can happen by mistake or when a gem repo has a gem with the same name as a local gem
|
||||
AZ_TracePrintf("GemModel", "Ignoring duplicate gem: %s", gemInfo.m_name.toUtf8().constData());
|
||||
return;
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
QStandardItem* item = new QStandardItem();
|
||||
@@ -61,11 +61,28 @@ namespace O3DE::ProjectManager
|
||||
item->setData(gemInfo.m_downloadStatus, RoleDownloadStatus);
|
||||
item->setData(gemInfo.m_licenseText, RoleLicenseText);
|
||||
item->setData(gemInfo.m_licenseLink, RoleLicenseLink);
|
||||
item->setData(gemInfo.m_repoUri, RoleRepoUri);
|
||||
|
||||
appendRow(item);
|
||||
|
||||
const QModelIndex modelIndex = index(rowCount()-1, 0);
|
||||
m_nameToIndexMap[gemInfo.m_name] = modelIndex;
|
||||
|
||||
return modelIndex;
|
||||
}
|
||||
|
||||
void GemModel::RemoveGem(const QModelIndex& modelIndex)
|
||||
{
|
||||
removeRow(modelIndex.row());
|
||||
}
|
||||
|
||||
void GemModel::RemoveGem(const QString& gemName)
|
||||
{
|
||||
auto nameFind = m_nameToIndexMap.find(gemName);
|
||||
if (nameFind != m_nameToIndexMap.end())
|
||||
{
|
||||
removeRow(nameFind->row());
|
||||
}
|
||||
}
|
||||
|
||||
void GemModel::Clear()
|
||||
@@ -255,6 +272,11 @@ namespace O3DE::ProjectManager
|
||||
return modelIndex.data(RoleLicenseLink).toString();
|
||||
}
|
||||
|
||||
QString GemModel::GetRepoUri(const QModelIndex& modelIndex)
|
||||
{
|
||||
return modelIndex.data(RoleRepoUri).toString();
|
||||
}
|
||||
|
||||
GemModel* GemModel::GetSourceModel(QAbstractItemModel* model)
|
||||
{
|
||||
GemSortFilterProxyModel* proxyModel = qobject_cast<GemSortFilterProxyModel*>(model);
|
||||
@@ -335,6 +357,8 @@ namespace O3DE::ProjectManager
|
||||
if (!IsAdded(dependency))
|
||||
{
|
||||
numChangedDependencies++;
|
||||
const QString dependencyName = gemModel->GetName(dependency);
|
||||
gemModel->emit dependencyGemStatusChanged(dependencyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -359,6 +383,8 @@ namespace O3DE::ProjectManager
|
||||
if (!IsAdded(dependency))
|
||||
{
|
||||
numChangedDependencies++;
|
||||
const QString dependencyName = gemModel->GetName(dependency);
|
||||
gemModel->emit dependencyGemStatusChanged(dependencyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -369,11 +395,30 @@ namespace O3DE::ProjectManager
|
||||
|
||||
void GemModel::OnRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last)
|
||||
{
|
||||
bool selectedRowRemoved = false;
|
||||
for (int i = first; i <= last; ++i)
|
||||
{
|
||||
QModelIndex modelIndex = index(i, 0, parent);
|
||||
const QString& gemName = GetName(modelIndex);
|
||||
m_nameToIndexMap.remove(gemName);
|
||||
|
||||
if (GetSelectionModel()->isRowSelected(i))
|
||||
{
|
||||
selectedRowRemoved = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Select a valid row if currently selected row was removed
|
||||
if (selectedRowRemoved)
|
||||
{
|
||||
for (const QModelIndex& index : m_nameToIndexMap)
|
||||
{
|
||||
if (index.isValid())
|
||||
{
|
||||
GetSelectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,6 +483,23 @@ namespace O3DE::ProjectManager
|
||||
return previouslyAdded && !added;
|
||||
}
|
||||
|
||||
void GemModel::DeactivateDependentGems(QAbstractItemModel& model, const QModelIndex& modelIndex)
|
||||
{
|
||||
GemModel* gemModel = GetSourceModel(&model);
|
||||
AZ_Assert(gemModel, "Failed to obtain GemModel");
|
||||
|
||||
QVector<QModelIndex> dependentGems = gemModel->GatherDependentGems(modelIndex);
|
||||
if (!dependentGems.isEmpty())
|
||||
{
|
||||
// we need to deactivate all gems that depend on this one
|
||||
for (auto dependentModelIndex : dependentGems)
|
||||
{
|
||||
SetIsAdded(model, dependentModelIndex, false);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void GemModel::SetDownloadStatus(QAbstractItemModel& model, const QModelIndex& modelIndex, GemInfo::DownloadStatus status)
|
||||
{
|
||||
model.setData(modelIndex, status, RoleDownloadStatus);
|
||||
|
||||
@@ -51,10 +51,13 @@ namespace O3DE::ProjectManager
|
||||
RoleRequirement,
|
||||
RoleDownloadStatus,
|
||||
RoleLicenseText,
|
||||
RoleLicenseLink
|
||||
RoleLicenseLink,
|
||||
RoleRepoUri
|
||||
};
|
||||
|
||||
void AddGem(const GemInfo& gemInfo);
|
||||
QModelIndex AddGem(const GemInfo& gemInfo);
|
||||
void RemoveGem(const QModelIndex& modelIndex);
|
||||
void RemoveGem(const QString& gemName);
|
||||
void Clear();
|
||||
void UpdateGemDependencies();
|
||||
|
||||
@@ -80,6 +83,7 @@ namespace O3DE::ProjectManager
|
||||
static QString GetRequirement(const QModelIndex& modelIndex);
|
||||
static QString GetLicenseText(const QModelIndex& modelIndex);
|
||||
static QString GetLicenseLink(const QModelIndex& modelIndex);
|
||||
static QString GetRepoUri(const QModelIndex& modelIndex);
|
||||
static GemModel* GetSourceModel(QAbstractItemModel* model);
|
||||
static const GemModel* GetSourceModel(const QAbstractItemModel* model);
|
||||
|
||||
@@ -95,6 +99,7 @@ namespace O3DE::ProjectManager
|
||||
static bool NeedsToBeRemoved(const QModelIndex& modelIndex, bool includeDependencies = false);
|
||||
static bool HasRequirement(const QModelIndex& modelIndex);
|
||||
static void UpdateDependencies(QAbstractItemModel& model, const QString& gemName, bool isAdded);
|
||||
static void DeactivateDependentGems(QAbstractItemModel& model, const QModelIndex& modelIndex);
|
||||
static void SetDownloadStatus(QAbstractItemModel& model, const QModelIndex& modelIndex, GemInfo::DownloadStatus status);
|
||||
|
||||
bool DoGemsToBeAddedHaveRequirements() const;
|
||||
@@ -109,6 +114,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
signals:
|
||||
void gemStatusChanged(const QString& gemName, uint32_t numChangedDependencies);
|
||||
void dependencyGemStatusChanged(const QString& gemName);
|
||||
|
||||
protected slots:
|
||||
void OnRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last);
|
||||
|
||||
@@ -204,9 +204,12 @@ namespace O3DE::ProjectManager
|
||||
emit OnInvalidated();
|
||||
}
|
||||
|
||||
void GemSortFilterProxyModel::ResetFilters()
|
||||
void GemSortFilterProxyModel::ResetFilters(bool clearSearchString)
|
||||
{
|
||||
m_searchString.clear();
|
||||
if (clearSearchString)
|
||||
{
|
||||
m_searchString.clear();
|
||||
}
|
||||
m_gemSelectedFilter = GemSelected::NoFilter;
|
||||
m_gemActiveFilter = GemActive::NoFilter;
|
||||
m_gemOriginFilter = {};
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace O3DE::ProjectManager
|
||||
void SetFeatures(const QSet<QString>& features) { m_featureFilter = features; InvalidateFilter(); }
|
||||
|
||||
void InvalidateFilter();
|
||||
void ResetFilters();
|
||||
void ResetFilters(bool clearSearchString = true);
|
||||
|
||||
signals:
|
||||
void OnInvalidated();
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <GemCatalog/GemUninstallDialog.h>
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QPushButton>
|
||||
#include <QVariant>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
GemUninstallDialog::GemUninstallDialog(const QString& gemName, QWidget* parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
setWindowTitle(tr("Uninstall Remote Gem"));
|
||||
setObjectName("GemUninstallDialog");
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
setModal(true);
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
layout->setMargin(30);
|
||||
layout->setAlignment(Qt::AlignTop);
|
||||
setLayout(layout);
|
||||
|
||||
// Body
|
||||
QLabel* subTitleLabel = new QLabel(tr("Are you sure you want to uninstall %1?").arg(gemName));
|
||||
subTitleLabel->setObjectName("gemCatalogDialogSubTitle");
|
||||
layout->addWidget(subTitleLabel);
|
||||
|
||||
layout->addSpacing(10);
|
||||
|
||||
QLabel* bodyLabel = new QLabel(tr("The Gem and its related files will be uninstalled. This does not affect the Gem's repository. "
|
||||
"You can reinstall this Gem from the Catalog, but its contents may be subject to change."));
|
||||
bodyLabel->setWordWrap(true);
|
||||
bodyLabel->setFixedSize(QSize(440, 80));
|
||||
layout->addWidget(bodyLabel);
|
||||
|
||||
layout->addSpacing(40);
|
||||
|
||||
// Buttons
|
||||
QDialogButtonBox* dialogButtons = new QDialogButtonBox();
|
||||
dialogButtons->setObjectName("footer");
|
||||
layout->addWidget(dialogButtons);
|
||||
|
||||
QPushButton* cancelButton = dialogButtons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole);
|
||||
cancelButton->setProperty("secondary", true);
|
||||
QPushButton* uninstallButton = dialogButtons->addButton(tr("Uninstall Gem"), QDialogButtonBox::ApplyRole);
|
||||
uninstallButton->setObjectName("gemCatalogUninstallGemButton");
|
||||
|
||||
connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject);
|
||||
connect(uninstallButton, &QPushButton::clicked, this, &QDialog::accept);
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <QDialog>
|
||||
#endif
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
class GemUninstallDialog
|
||||
: public QDialog
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
public:
|
||||
explicit GemUninstallDialog(const QString& gemName, QWidget *parent = nullptr);
|
||||
~GemUninstallDialog() = default;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <GemCatalog/GemUpdateDialog.h>
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <QVariant>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
GemUpdateDialog::GemUpdateDialog(const QString& gemName, bool updateAvaliable, QWidget* parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
setWindowTitle(tr("Update Remote Gem"));
|
||||
setObjectName("GemUpdateDialog");
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
setModal(true);
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
layout->setMargin(30);
|
||||
layout->setAlignment(Qt::AlignTop);
|
||||
setLayout(layout);
|
||||
|
||||
// Body
|
||||
QLabel* subTitleLabel = new QLabel(tr("%1 to the latest version of %2?").arg(
|
||||
updateAvaliable ? tr("Update") : tr("Force update"), gemName));
|
||||
subTitleLabel->setObjectName("gemCatalogDialogSubTitle");
|
||||
layout->addWidget(subTitleLabel);
|
||||
|
||||
layout->addSpacing(10);
|
||||
|
||||
QLabel* bodyLabel = new QLabel(tr("%1The latest version of this Gem may not be compatible with your engine. "
|
||||
"Updating this Gem will remove any local changes made to this Gem, "
|
||||
"and may remove old features that are in use.").arg(
|
||||
updateAvaliable ? "" : tr("No update detected for Gem. "
|
||||
"This will force a re-download of the gem. ")));
|
||||
bodyLabel->setWordWrap(true);
|
||||
bodyLabel->setFixedSize(QSize(440, 80));
|
||||
layout->addWidget(bodyLabel);
|
||||
|
||||
layout->addSpacing(40);
|
||||
|
||||
// Buttons
|
||||
QDialogButtonBox* dialogButtons = new QDialogButtonBox();
|
||||
dialogButtons->setObjectName("footer");
|
||||
layout->addWidget(dialogButtons);
|
||||
|
||||
QPushButton* cancelButton = dialogButtons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole);
|
||||
cancelButton->setProperty("secondary", true);
|
||||
QPushButton* updateButton =
|
||||
dialogButtons->addButton(tr("%1Update Gem").arg(updateAvaliable ? "" : tr("Force ")), QDialogButtonBox::ApplyRole);
|
||||
|
||||
connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject);
|
||||
connect(updateButton, &QPushButton::clicked, this, &QDialog::accept);
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <QDialog>
|
||||
#endif
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
class GemUpdateDialog
|
||||
: public QDialog
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
public :
|
||||
explicit GemUpdateDialog(const QString& gemName, bool updateAvaliable = true, QWidget* parent = nullptr);
|
||||
~GemUpdateDialog() = default;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -37,7 +37,7 @@ namespace O3DE::ProjectManager
|
||||
QString m_additionalInfo = "";
|
||||
QString m_directoryLink = "";
|
||||
QString m_repoUri = "";
|
||||
QStringList m_includedGemPaths = {};
|
||||
QStringList m_includedGemUris = {};
|
||||
QDateTime m_lastUpdated;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <GemRepo/GemRepoInspector.h>
|
||||
#include <GemRepo/GemRepoItemDelegate.h>
|
||||
#include <PythonBindingsInterface.h>
|
||||
|
||||
#include <QFrame>
|
||||
#include <QLabel>
|
||||
@@ -60,8 +61,10 @@ namespace O3DE::ProjectManager
|
||||
|
||||
// Repo name and url link
|
||||
m_nameLabel->setText(m_model->GetName(modelIndex));
|
||||
m_repoLinkLabel->setText(m_model->GetRepoUri(modelIndex));
|
||||
m_repoLinkLabel->SetUrl(m_model->GetRepoUri(modelIndex));
|
||||
|
||||
const QString repoUri = m_model->GetRepoUri(modelIndex);
|
||||
m_repoLinkLabel->setText(repoUri);
|
||||
m_repoLinkLabel->SetUrl(repoUri);
|
||||
|
||||
// Repo summary
|
||||
m_summaryLabel->setText(m_model->GetSummary(modelIndex));
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace O3DE::ProjectManager
|
||||
item->setData(gemRepoInfo.m_lastUpdated, RoleLastUpdated);
|
||||
item->setData(gemRepoInfo.m_path, RolePath);
|
||||
item->setData(gemRepoInfo.m_additionalInfo, RoleAdditionalInfo);
|
||||
item->setData(gemRepoInfo.m_includedGemPaths, RoleIncludedGems);
|
||||
item->setData(gemRepoInfo.m_includedGemUris, RoleIncludedGems);
|
||||
|
||||
appendRow(item);
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace O3DE::ProjectManager
|
||||
return modelIndex.data(RolePath).toString();
|
||||
}
|
||||
|
||||
QStringList GemRepoModel::GetIncludedGemPaths(const QModelIndex& modelIndex)
|
||||
QStringList GemRepoModel::GetIncludedGemUris(const QModelIndex& modelIndex)
|
||||
{
|
||||
return modelIndex.data(RoleIncludedGems).toStringList();
|
||||
}
|
||||
@@ -118,23 +118,19 @@ namespace O3DE::ProjectManager
|
||||
|
||||
QVector<GemInfo> GemRepoModel::GetIncludedGemInfos(const QModelIndex& modelIndex)
|
||||
{
|
||||
QVector<GemInfo> allGemInfos;
|
||||
QStringList repoGemPaths = GetIncludedGemPaths(modelIndex);
|
||||
QString repoUri = GetRepoUri(modelIndex);
|
||||
|
||||
for (const QString& gemPath : repoGemPaths)
|
||||
const AZ::Outcome<QVector<GemInfo>, AZStd::string>& gemInfosResult = PythonBindingsInterface::Get()->GetGemInfosForRepo(repoUri);
|
||||
if (gemInfosResult.IsSuccess())
|
||||
{
|
||||
AZ::Outcome<GemInfo> gemInfoResult = PythonBindingsInterface::Get()->GetGemInfo(gemPath);
|
||||
if (gemInfoResult.IsSuccess())
|
||||
{
|
||||
allGemInfos.append(gemInfoResult.GetValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(nullptr, tr("Gem Not Found"), tr("Cannot find info for gem %1.").arg(gemPath));
|
||||
}
|
||||
return gemInfosResult.GetValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(nullptr, tr("Gems not found"), tr("Cannot find info for gems from repo %1").arg(GetName(modelIndex)));
|
||||
}
|
||||
|
||||
return allGemInfos;
|
||||
return QVector<GemInfo>();
|
||||
}
|
||||
|
||||
bool GemRepoModel::IsEnabled(const QModelIndex& modelIndex)
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace O3DE::ProjectManager
|
||||
static QDateTime GetLastUpdated(const QModelIndex& modelIndex);
|
||||
static QString GetPath(const QModelIndex& modelIndex);
|
||||
|
||||
static QStringList GetIncludedGemPaths(const QModelIndex& modelIndex);
|
||||
static QStringList GetIncludedGemUris(const QModelIndex& modelIndex);
|
||||
static QVector<Tag> GetIncludedGemTags(const QModelIndex& modelIndex);
|
||||
static QVector<GemInfo> GetIncludedGemInfos(const QModelIndex& modelIndex);
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace O3DE::ProjectManager
|
||||
// Select the first entry after everything got correctly sized
|
||||
QTimer::singleShot(200, [=]{
|
||||
QModelIndex firstModelIndex = m_gemRepoListView->model()->index(0,0);
|
||||
m_gemRepoListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect);
|
||||
m_gemRepoListView->selectionModel()->setCurrentIndex(firstModelIndex, QItemSelectionModel::ClearAndSelect);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -92,8 +92,9 @@ namespace O3DE::ProjectManager
|
||||
return;
|
||||
}
|
||||
|
||||
bool addGemRepoResult = PythonBindingsInterface::Get()->AddGemRepo(repoUri);
|
||||
if (addGemRepoResult)
|
||||
AZ::Outcome < void,
|
||||
AZStd::pair<AZStd::string, AZStd::string>> addGemRepoResult = PythonBindingsInterface::Get()->AddGemRepo(repoUri);
|
||||
if (addGemRepoResult.IsSuccess())
|
||||
{
|
||||
Reinit();
|
||||
emit OnRefresh();
|
||||
@@ -101,8 +102,21 @@ namespace O3DE::ProjectManager
|
||||
else
|
||||
{
|
||||
QString failureMessage = tr("Failed to add gem repo: %1.").arg(repoUri);
|
||||
QMessageBox::critical(this, tr("Operation failed"), failureMessage);
|
||||
AZ_Error("Project Manger", false, failureMessage.toUtf8());
|
||||
if (!addGemRepoResult.GetError().second.empty())
|
||||
{
|
||||
QMessageBox addRepoError;
|
||||
addRepoError.setIcon(QMessageBox::Critical);
|
||||
addRepoError.setWindowTitle(failureMessage);
|
||||
addRepoError.setText(addGemRepoResult.GetError().first.c_str());
|
||||
addRepoError.setDetailedText(addGemRepoResult.GetError().second.c_str());
|
||||
addRepoError.exec();
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(this, failureMessage, addGemRepoResult.GetError().first.c_str());
|
||||
}
|
||||
|
||||
AZ_Error("Project Manager", false, failureMessage.toUtf8());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,14 @@
|
||||
#include <ProjectBuilderController.h>
|
||||
#include <ProjectBuilderWorker.h>
|
||||
#include <ProjectButtonWidget.h>
|
||||
#include <ProjectManagerSettings.h>
|
||||
|
||||
#include <AzCore/Settings/SettingsRegistry.h>
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QDesktopServices>
|
||||
#include <QUrl>
|
||||
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
ProjectBuilderController::ProjectBuilderController(const ProjectInfo& projectInfo, ProjectButton* projectButton, QWidget* parent)
|
||||
@@ -27,6 +29,15 @@ namespace O3DE::ProjectManager
|
||||
m_worker = new ProjectBuilderWorker(m_projectInfo);
|
||||
m_worker->moveToThread(&m_workerThread);
|
||||
|
||||
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
if (settingsRegistry)
|
||||
{
|
||||
// Remove key here in case Project Manager crashing while building that causes HandleResults to not be called
|
||||
QString settingsKey = GetProjectBuiltSuccessfullyKey(m_projectInfo.m_projectName);
|
||||
settingsRegistry->Remove(settingsKey.toStdString().c_str());
|
||||
SaveProjectManagerSettings();
|
||||
}
|
||||
|
||||
connect(&m_workerThread, &QThread::finished, m_worker, &ProjectBuilderWorker::deleteLater);
|
||||
connect(&m_workerThread, &QThread::started, m_worker, &ProjectBuilderWorker::BuildProject);
|
||||
connect(m_worker, &ProjectBuilderWorker::Done, this, &ProjectBuilderController::HandleResults);
|
||||
@@ -80,6 +91,8 @@ namespace O3DE::ProjectManager
|
||||
|
||||
void ProjectBuilderController::HandleResults(const QString& result)
|
||||
{
|
||||
QString settingsKey = GetProjectBuiltSuccessfullyKey(m_projectInfo.m_projectName);
|
||||
|
||||
if (!result.isEmpty())
|
||||
{
|
||||
if (result.contains(tr("log")))
|
||||
@@ -109,12 +122,26 @@ namespace O3DE::ProjectManager
|
||||
emit NotifyBuildProject(m_projectInfo);
|
||||
}
|
||||
|
||||
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
if (settingsRegistry)
|
||||
{
|
||||
settingsRegistry->Remove(settingsKey.toStdString().c_str());
|
||||
SaveProjectManagerSettings();
|
||||
}
|
||||
|
||||
emit Done(false);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_projectInfo.m_buildFailed = false;
|
||||
|
||||
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
if (settingsRegistry)
|
||||
{
|
||||
settingsRegistry->Set(settingsKey.toStdString().c_str(), true);
|
||||
SaveProjectManagerSettings();
|
||||
}
|
||||
}
|
||||
|
||||
emit Done(true);
|
||||
|
||||
@@ -8,7 +8,11 @@
|
||||
|
||||
#include <ProjectButtonWidget.h>
|
||||
#include <ProjectManagerDefs.h>
|
||||
#include <ProjectUtils.h>
|
||||
#include <ProjectManager_Traits_Platform.h>
|
||||
#include <AzQtComponents/Utilities/DesktopUtilities.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
@@ -23,6 +27,7 @@
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QDesktopServices>
|
||||
#include <QMessageBox>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -198,6 +203,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
QMenu* menu = new QMenu(this);
|
||||
menu->addAction(tr("Edit Project Settings..."), this, [this]() { emit EditProject(m_projectInfo.m_path); });
|
||||
menu->addAction(tr("Configure Gems..."), this, [this]() { emit EditProjectGems(m_projectInfo.m_path); });
|
||||
menu->addAction(tr("Build"), this, [this]() { emit BuildProject(m_projectInfo); });
|
||||
menu->addAction(tr("Open CMake GUI..."), this, [this]() { emit OpenCMakeGUI(m_projectInfo); });
|
||||
menu->addSeparator();
|
||||
@@ -205,6 +211,29 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
AzQtComponents::ShowFileOnDesktop(m_projectInfo.m_path);
|
||||
});
|
||||
|
||||
#if AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT
|
||||
menu->addAction(tr("Create Editor desktop shortcut..."), this, [this]()
|
||||
{
|
||||
AZ::IO::FixedMaxPath executableDirectory = ProjectUtils::GetEditorDirectory();
|
||||
AZStd::string executableFilename = "Editor";
|
||||
AZ::IO::FixedMaxPath editorExecutablePath = executableDirectory / (executableFilename + AZ_TRAIT_OS_EXECUTABLE_EXTENSION);
|
||||
|
||||
const QString shortcutName = QString("%1 Editor").arg(m_projectInfo.m_displayName);
|
||||
const QString arg = QString("--regset=\"/Amazon/AzCore/Bootstrap/project_path=%1\"").arg(m_projectInfo.m_path);
|
||||
|
||||
auto result = ProjectUtils::CreateDesktopShortcut(shortcutName, editorExecutablePath.c_str(), { arg });
|
||||
if(result.IsSuccess())
|
||||
{
|
||||
QMessageBox::information(this, tr("Desktop Shortcut Created"), result.GetValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(this, tr("Failed to create shortcut"), result.GetError());
|
||||
}
|
||||
});
|
||||
#endif // AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT
|
||||
|
||||
menu->addSeparator();
|
||||
menu->addAction(tr("Duplicate"), this, [this]() { emit CopyProject(m_projectInfo); });
|
||||
menu->addSeparator();
|
||||
|
||||
@@ -95,6 +95,7 @@ namespace O3DE::ProjectManager
|
||||
signals:
|
||||
void OpenProject(const QString& projectName);
|
||||
void EditProject(const QString& projectName);
|
||||
void EditProjectGems(const QString& projectName);
|
||||
void CopyProject(const ProjectInfo& projectInfo);
|
||||
void RemoveProject(const QString& projectName);
|
||||
void DeleteProject(const QString& projectName);
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ProjectManagerSettings.h"
|
||||
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/IO/ByteContainerStream.h>
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
void SaveProjectManagerSettings()
|
||||
{
|
||||
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
AZ::SettingsRegistryMergeUtils::DumperSettings dumperSettings;
|
||||
dumperSettings.m_prettifyOutput = true;
|
||||
dumperSettings.m_jsonPointerPrefix = ProjectManagerKeyPrefix;
|
||||
|
||||
AZStd::string stringBuffer;
|
||||
AZ::IO::ByteContainerStream stringStream(&stringBuffer);
|
||||
if (!AZ::SettingsRegistryMergeUtils::DumpSettingsRegistryToStream(
|
||||
*settingsRegistry, ProjectManagerKeyPrefix, stringStream, dumperSettings))
|
||||
{
|
||||
AZ_Warning("ProjectManager", false, "Could not save Project Manager settings to stream");
|
||||
return;
|
||||
}
|
||||
|
||||
AZ::IO::FixedMaxPath o3deUserPath = AZ::Utils::GetO3deManifestDirectory();
|
||||
o3deUserPath /= AZ::SettingsRegistryInterface::RegistryFolder;
|
||||
o3deUserPath /= "ProjectManager.setreg";
|
||||
|
||||
bool saved = false;
|
||||
constexpr auto configurationMode =
|
||||
AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY;
|
||||
|
||||
AZ::IO::SystemFile outputFile;
|
||||
if (outputFile.Open(o3deUserPath.c_str(), configurationMode))
|
||||
{
|
||||
saved = outputFile.Write(stringBuffer.data(), stringBuffer.size()) == stringBuffer.size();
|
||||
}
|
||||
|
||||
AZ_Warning("ProjectManager", saved, "Unable to save Project Manager registry file to path: %s", o3deUserPath.c_str());
|
||||
}
|
||||
|
||||
QString GetProjectBuiltSuccessfullyKey(const QString& projectName)
|
||||
{
|
||||
return QString("%1/Projects/%2/BuiltSuccessfully").arg(ProjectManagerKeyPrefix).arg(projectName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <QString>
|
||||
#endif
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
static constexpr char ProjectManagerKeyPrefix[] = "/O3DE/ProjectManager";
|
||||
|
||||
void SaveProjectManagerSettings();
|
||||
QString GetProjectBuiltSuccessfullyKey(const QString& projectName);
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QStandardPaths>
|
||||
#include <QScrollArea>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -33,11 +34,23 @@ namespace O3DE::ProjectManager
|
||||
// if we don't set this in a frame (just use a sub-layout) all the content will align incorrectly horizontally
|
||||
QFrame* projectSettingsFrame = new QFrame(this);
|
||||
projectSettingsFrame->setObjectName("projectSettings");
|
||||
m_verticalLayout = new QVBoxLayout();
|
||||
|
||||
// you cannot remove content margins in qss
|
||||
m_verticalLayout->setContentsMargins(0, 0, 0, 0);
|
||||
QVBoxLayout* vLayout = new QVBoxLayout();
|
||||
vLayout->setMargin(0);
|
||||
vLayout->setAlignment(Qt::AlignTop);
|
||||
projectSettingsFrame->setLayout(vLayout);
|
||||
|
||||
QScrollArea* scrollArea = new QScrollArea(this);
|
||||
scrollArea->setWidgetResizable(true);
|
||||
vLayout->addWidget(scrollArea);
|
||||
|
||||
QWidget* scrollWidget = new QWidget(this);
|
||||
scrollArea->setWidget(scrollWidget);
|
||||
|
||||
m_verticalLayout = new QVBoxLayout();
|
||||
m_verticalLayout->setMargin(0);
|
||||
m_verticalLayout->setAlignment(Qt::AlignTop);
|
||||
scrollWidget->setLayout(m_verticalLayout);
|
||||
|
||||
m_projectName = new FormLineEditWidget(tr("Project name"), "", this);
|
||||
connect(m_projectName->lineEdit(), &QLineEdit::textChanged, this, &ProjectSettingsScreen::OnProjectNameUpdated);
|
||||
|
||||
@@ -628,11 +628,11 @@ namespace O3DE::ProjectManager
|
||||
return AZ::Failure(QObject::tr("Process for command '%1' timed out at %2 seconds").arg(cmd).arg(commandTimeoutSeconds));
|
||||
}
|
||||
int resultCode = execProcess.exitCode();
|
||||
QString resultOutput = execProcess.readAllStandardOutput();
|
||||
if (resultCode != 0)
|
||||
{
|
||||
return AZ::Failure(QObject::tr("Process for command '%1' failed (result code %2").arg(cmd).arg(resultCode));
|
||||
return AZ::Failure(QObject::tr("Process for command '%1' failed (result code %2) %3").arg(cmd).arg(resultCode).arg(resultOutput));
|
||||
}
|
||||
QString resultOutput = execProcess.readAllStandardOutput();
|
||||
return AZ::Success(resultOutput);
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,15 @@ namespace O3DE::ProjectManager
|
||||
AZ::Outcome<QString, QString> GetProjectBuildPath(const QString& projectPath);
|
||||
AZ::Outcome<void, QString> OpenCMakeGUI(const QString& projectPath);
|
||||
AZ::Outcome<QString, QString> RunGetPythonScript(const QString& enginePath);
|
||||
|
||||
/**
|
||||
* Create a desktop shortcut.
|
||||
* @param filename the name of the desktop shorcut file
|
||||
* @param target the path to the target to run
|
||||
* @param arguments the argument list to provide to the target
|
||||
* @return AZ::Outcome with the command result on success
|
||||
*/
|
||||
AZ::Outcome<QString, QString> CreateDesktopShortcut(const QString& filename, const QString& targetPath, const QStringList& arguments);
|
||||
|
||||
AZ::IO::FixedMaxPath GetEditorDirectory();
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <ProjectUtils.h>
|
||||
#include <ProjectBuilderController.h>
|
||||
#include <ScreensCtrl.h>
|
||||
#include <ProjectManagerSettings.h>
|
||||
|
||||
#include <AzQtComponents/Components/FlowLayout.h>
|
||||
#include <AzCore/Platform.h>
|
||||
@@ -22,6 +23,7 @@
|
||||
#include <AzFramework/Process/ProcessCommon.h>
|
||||
#include <AzFramework/Process/ProcessWatcher.h>
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
#include <AzCore/Settings/SettingsRegistry.h>
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
@@ -181,6 +183,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
connect(projectButton, &ProjectButton::OpenProject, this, &ProjectsScreen::HandleOpenProject);
|
||||
connect(projectButton, &ProjectButton::EditProject, this, &ProjectsScreen::HandleEditProject);
|
||||
connect(projectButton, &ProjectButton::EditProjectGems, this, &ProjectsScreen::HandleEditProjectGems);
|
||||
connect(projectButton, &ProjectButton::CopyProject, this, &ProjectsScreen::HandleCopyProject);
|
||||
connect(projectButton, &ProjectButton::RemoveProject, this, &ProjectsScreen::HandleRemoveProject);
|
||||
connect(projectButton, &ProjectButton::DeleteProject, this, &ProjectsScreen::HandleDeleteProject);
|
||||
@@ -269,17 +272,36 @@ namespace O3DE::ProjectManager
|
||||
// Add any missing project buttons and restore buttons to default state
|
||||
for (const ProjectInfo& project : projectsVector)
|
||||
{
|
||||
ProjectButton* currentButton = nullptr;
|
||||
if (!m_projectButtons.contains(QDir::toNativeSeparators(project.m_path)))
|
||||
{
|
||||
m_projectButtons.insert(QDir::toNativeSeparators(project.m_path), CreateProjectButton(project));
|
||||
currentButton = CreateProjectButton(project);
|
||||
m_projectButtons.insert(QDir::toNativeSeparators(project.m_path), currentButton);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto projectButtonIter = m_projectButtons.find(QDir::toNativeSeparators(project.m_path));
|
||||
if (projectButtonIter != m_projectButtons.end())
|
||||
{
|
||||
projectButtonIter.value()->RestoreDefaultState();
|
||||
m_projectsFlowLayout->addWidget(projectButtonIter.value());
|
||||
currentButton = projectButtonIter.value();
|
||||
currentButton->RestoreDefaultState();
|
||||
m_projectsFlowLayout->addWidget(currentButton);
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether project manager has successfully built the project
|
||||
if (currentButton)
|
||||
{
|
||||
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
bool projectBuiltSuccessfully = false;
|
||||
if (settingsRegistry)
|
||||
{
|
||||
QString settingsKey = GetProjectBuiltSuccessfullyKey(project.m_projectName);
|
||||
settingsRegistry->Get(projectBuiltSuccessfully, settingsKey.toStdString().c_str());
|
||||
}
|
||||
if (!projectBuiltSuccessfully)
|
||||
{
|
||||
currentButton->ShowBuildRequired();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -448,6 +470,14 @@ namespace O3DE::ProjectManager
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::UpdateProject);
|
||||
}
|
||||
}
|
||||
void ProjectsScreen::HandleEditProjectGems(const QString& projectPath)
|
||||
{
|
||||
if (!WarnIfInBuildQueue(projectPath))
|
||||
{
|
||||
emit NotifyCurrentProject(projectPath);
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::GemCatalog);
|
||||
}
|
||||
}
|
||||
void ProjectsScreen::HandleCopyProject(const ProjectInfo& projectInfo)
|
||||
{
|
||||
if (!WarnIfInBuildQueue(projectInfo.m_path))
|
||||
|
||||
@@ -46,6 +46,7 @@ namespace O3DE::ProjectManager
|
||||
void HandleAddProjectButton();
|
||||
void HandleOpenProject(const QString& projectPath);
|
||||
void HandleEditProject(const QString& projectPath);
|
||||
void HandleEditProjectGems(const QString& projectPath);
|
||||
void HandleCopyProject(const ProjectInfo& projectInfo);
|
||||
void HandleRemoveProject(const QString& projectPath);
|
||||
void HandleDeleteProject(const QString& projectPath);
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/std/containers/unordered_set.h>
|
||||
#include <AzCore/std/string/conversions.h>
|
||||
#include <AzCore/std/numeric.h>
|
||||
#include <AzCore/StringFunc/StringFunc.h>
|
||||
|
||||
#include <QDir>
|
||||
@@ -210,6 +211,16 @@ namespace RedirectOutput
|
||||
});
|
||||
|
||||
SetRedirection("stderr", g_redirect_stderr_saved, g_redirect_stderr, []([[maybe_unused]] const char* msg) {
|
||||
AZStd::string lastPythonError = msg;
|
||||
constexpr const char* pythonErrorPrefix = "ERROR:root:";
|
||||
constexpr size_t lengthOfErrorPrefix = AZStd::char_traits<char>::length(pythonErrorPrefix);
|
||||
auto errorPrefix = lastPythonError.find(pythonErrorPrefix);
|
||||
if (errorPrefix != AZStd::string::npos)
|
||||
{
|
||||
lastPythonError.erase(errorPrefix, lengthOfErrorPrefix);
|
||||
}
|
||||
O3DE::ProjectManager::PythonBindingsInterface::Get()->AddErrorString(lastPythonError);
|
||||
|
||||
AZ_TracePrintf("Python", msg);
|
||||
});
|
||||
|
||||
@@ -376,6 +387,8 @@ namespace O3DE::ProjectManager
|
||||
pybind11::gil_scoped_release release;
|
||||
pybind11::gil_scoped_acquire acquire;
|
||||
|
||||
ClearErrorStrings();
|
||||
|
||||
try
|
||||
{
|
||||
executionCallback();
|
||||
@@ -515,7 +528,11 @@ namespace O3DE::ProjectManager
|
||||
auto pyProjectPath = QString_To_Py_Path(projectPath);
|
||||
for (auto path : m_manifest.attr("get_all_gems")(pyProjectPath))
|
||||
{
|
||||
gems.push_back(GemInfoFromPath(path, pyProjectPath));
|
||||
GemInfo gemInfo = GemInfoFromPath(path, pyProjectPath);
|
||||
// Mark as downloaded because this gem was registered with an existing directory
|
||||
gemInfo.m_downloadStatus = GemInfo::DownloadStatus::Downloaded;
|
||||
|
||||
gems.push_back(AZStd::move(gemInfo));
|
||||
}
|
||||
});
|
||||
if (!result.IsSuccess())
|
||||
@@ -560,7 +577,7 @@ namespace O3DE::ProjectManager
|
||||
return AZ::Success(AZStd::move(gemNames));
|
||||
}
|
||||
|
||||
AZ::Outcome<void, AZStd::string> PythonBindings::RegisterGem(const QString& gemPath, const QString& projectPath)
|
||||
AZ::Outcome<void, AZStd::string> PythonBindings::GemRegistration(const QString& gemPath, const QString& projectPath, bool remove)
|
||||
{
|
||||
bool registrationResult = false;
|
||||
auto result = ExecuteWithLockErrorHandling(
|
||||
@@ -582,7 +599,8 @@ namespace O3DE::ProjectManager
|
||||
pybind11::none(), // default_restricted_folder
|
||||
pybind11::none(), // default_third_party_folder
|
||||
pybind11::none(), // external_subdir_engine_path
|
||||
externalProjectPath // external_subdir_project_path
|
||||
externalProjectPath, // external_subdir_project_path
|
||||
remove // remove
|
||||
);
|
||||
|
||||
// Returns an exit code so boolify it then invert result
|
||||
@@ -595,12 +613,23 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
else if (!registrationResult)
|
||||
{
|
||||
return AZ::Failure<AZStd::string>(AZStd::string::format("Failed to register gem path %s", gemPath.toUtf8().constData()));
|
||||
return AZ::Failure<AZStd::string>(AZStd::string::format(
|
||||
"Failed to %s gem path %s", remove ? "unregister" : "register", gemPath.toUtf8().constData()));
|
||||
}
|
||||
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
AZ::Outcome<void, AZStd::string> PythonBindings::RegisterGem(const QString& gemPath, const QString& projectPath)
|
||||
{
|
||||
return GemRegistration(gemPath, projectPath);
|
||||
}
|
||||
|
||||
AZ::Outcome<void, AZStd::string> PythonBindings::UnregisterGem(const QString& gemPath, const QString& projectPath)
|
||||
{
|
||||
return GemRegistration(gemPath, projectPath, /*remove*/true);
|
||||
}
|
||||
|
||||
bool PythonBindings::AddProject(const QString& path)
|
||||
{
|
||||
bool registrationResult = false;
|
||||
@@ -715,6 +744,7 @@ namespace O3DE::ProjectManager
|
||||
gemInfo.m_documentationLink = Py_To_String_Optional(data, "documentation_url", "");
|
||||
gemInfo.m_licenseText = Py_To_String_Optional(data, "license", "Unspecified License");
|
||||
gemInfo.m_licenseLink = Py_To_String_Optional(data, "license_url", "");
|
||||
gemInfo.m_repoUri = Py_To_String_Optional(data, "repo_uri", "");
|
||||
|
||||
if (gemInfo.m_creator.contains("Open 3D Engine"))
|
||||
{
|
||||
@@ -728,6 +758,11 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
gemInfo.m_gemOrigin = GemInfo::GemOrigin::Remote;
|
||||
}
|
||||
// If no origin was provided this cannot be remote and would be specified if O3DE so it should be local
|
||||
else
|
||||
{
|
||||
gemInfo.m_gemOrigin = GemInfo::GemOrigin::Local;
|
||||
}
|
||||
|
||||
// As long Base Open3DEngine gems are installed before first startup non-remote gems will be downloaded
|
||||
if (gemInfo.m_gemOrigin != GemInfo::GemOrigin::Remote)
|
||||
@@ -1029,7 +1064,7 @@ namespace O3DE::ProjectManager
|
||||
return result && refreshResult;
|
||||
}
|
||||
|
||||
bool PythonBindings::AddGemRepo(const QString& repoUri)
|
||||
AZ::Outcome<void, AZStd::pair<AZStd::string, AZStd::string>> PythonBindings::AddGemRepo(const QString& repoUri)
|
||||
{
|
||||
bool registrationResult = false;
|
||||
bool result = ExecuteWithLock(
|
||||
@@ -1043,7 +1078,12 @@ namespace O3DE::ProjectManager
|
||||
registrationResult = !pythonRegistrationResult.cast<bool>();
|
||||
});
|
||||
|
||||
return result && registrationResult;
|
||||
if (!result || !registrationResult)
|
||||
{
|
||||
return AZ::Failure<AZStd::pair<AZStd::string, AZStd::string>>(GetSimpleDetailedErrorPair());
|
||||
}
|
||||
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
bool PythonBindings::RemoveGemRepo(const QString& repoUri)
|
||||
@@ -1113,11 +1153,11 @@ namespace O3DE::ProjectManager
|
||||
gemRepoInfo.m_isEnabled = false;
|
||||
}
|
||||
|
||||
if (data.contains("gem_paths"))
|
||||
if (data.contains("gems"))
|
||||
{
|
||||
for (auto gemPath : data["gem_paths"])
|
||||
for (auto gemPath : data["gems"])
|
||||
{
|
||||
gemRepoInfo.m_includedGemPaths.push_back(Py_To_String(gemPath));
|
||||
gemRepoInfo.m_includedGemUris.push_back(Py_To_String(gemPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1166,49 +1206,35 @@ namespace O3DE::ProjectManager
|
||||
return AZ::Success(AZStd::move(gemRepos));
|
||||
}
|
||||
|
||||
AZ::Outcome<void, AZStd::string> PythonBindings::DownloadGem(const QString& gemName, std::function<void(int)> gemProgressCallback)
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> PythonBindings::GetGemInfosForRepo(const QString& repoUri)
|
||||
{
|
||||
// This process is currently limited to download a single gem at a time.
|
||||
bool downloadSucceeded = false;
|
||||
|
||||
m_requestCancelDownload = false;
|
||||
auto result = ExecuteWithLockErrorHandling(
|
||||
QVector<GemInfo> gemInfos;
|
||||
AZ::Outcome<void, AZStd::string> result = ExecuteWithLockErrorHandling(
|
||||
[&]
|
||||
{
|
||||
auto downloadResult = m_download.attr("download_gem")(
|
||||
QString_To_Py_String(gemName), // gem name
|
||||
pybind11::none(), // destination path
|
||||
false, // skip auto register
|
||||
pybind11::cpp_function(
|
||||
[this, gemProgressCallback](int progress)
|
||||
{
|
||||
gemProgressCallback(progress);
|
||||
auto pyUri = QString_To_Py_String(repoUri);
|
||||
auto gemPaths = m_repo.attr("get_gem_json_paths_from_cached_repo")(pyUri);
|
||||
|
||||
return m_requestCancelDownload;
|
||||
}) // Callback for download progress and cancelling
|
||||
);
|
||||
downloadSucceeded = (downloadResult.cast<int>() == 0);
|
||||
if (pybind11::isinstance<pybind11::set>(gemPaths))
|
||||
{
|
||||
for (auto path : gemPaths)
|
||||
{
|
||||
GemInfo gemInfo = GemInfoFromPath(path, pybind11::none());
|
||||
gemInfo.m_downloadStatus = GemInfo::DownloadStatus::NotDownloaded;
|
||||
gemInfos.push_back(gemInfo);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else if (!downloadSucceeded)
|
||||
{
|
||||
return AZ::Failure<AZStd::string>("Failed to download gem.");
|
||||
return AZ::Failure(result.GetError());
|
||||
}
|
||||
|
||||
return AZ::Success();
|
||||
return AZ::Success(AZStd::move(gemInfos));
|
||||
}
|
||||
|
||||
void PythonBindings::CancelDownload()
|
||||
{
|
||||
m_requestCancelDownload = true;
|
||||
}
|
||||
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> PythonBindings::GetAllGemRepoGemsInfos()
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> PythonBindings::GetGemInfosForAllRepos()
|
||||
{
|
||||
QVector<GemInfo> gemInfos;
|
||||
AZ::Outcome<void, AZStd::string> result = ExecuteWithLockErrorHandling(
|
||||
@@ -1234,4 +1260,84 @@ namespace O3DE::ProjectManager
|
||||
|
||||
return AZ::Success(AZStd::move(gemInfos));
|
||||
}
|
||||
|
||||
AZ::Outcome<void, AZStd::pair<AZStd::string, AZStd::string>> PythonBindings::DownloadGem(
|
||||
const QString& gemName, std::function<void(int, int)> gemProgressCallback, bool force)
|
||||
{
|
||||
// This process is currently limited to download a single gem at a time.
|
||||
bool downloadSucceeded = false;
|
||||
|
||||
m_requestCancelDownload = false;
|
||||
auto result = ExecuteWithLockErrorHandling(
|
||||
[&]
|
||||
{
|
||||
auto downloadResult = m_download.attr("download_gem")(
|
||||
QString_To_Py_String(gemName), // gem name
|
||||
pybind11::none(), // destination path
|
||||
false, // skip auto register
|
||||
force, // force overwrite
|
||||
pybind11::cpp_function(
|
||||
[this, gemProgressCallback](int bytesDownloaded, int totalBytes)
|
||||
{
|
||||
gemProgressCallback(bytesDownloaded, totalBytes);
|
||||
|
||||
return m_requestCancelDownload;
|
||||
}) // Callback for download progress and cancelling
|
||||
);
|
||||
downloadSucceeded = (downloadResult.cast<int>() == 0);
|
||||
});
|
||||
|
||||
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
AZStd::pair<AZStd::string, AZStd::string> pythonRunError(result.GetError(), result.GetError());
|
||||
return AZ::Failure<AZStd::pair<AZStd::string, AZStd::string>>(AZStd::move(pythonRunError));
|
||||
}
|
||||
else if (!downloadSucceeded)
|
||||
{
|
||||
return AZ::Failure<AZStd::pair<AZStd::string, AZStd::string>>(GetSimpleDetailedErrorPair());
|
||||
}
|
||||
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
void PythonBindings::CancelDownload()
|
||||
{
|
||||
m_requestCancelDownload = true;
|
||||
}
|
||||
|
||||
bool PythonBindings::IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated)
|
||||
{
|
||||
bool updateAvaliableResult = false;
|
||||
bool result = ExecuteWithLock(
|
||||
[&]
|
||||
{
|
||||
auto pyGemName = QString_To_Py_String(gemName);
|
||||
auto pyLastUpdated = QString_To_Py_String(lastUpdated);
|
||||
auto pythonUpdateAvaliableResult = m_download.attr("is_o3de_gem_update_available")(pyGemName, pyLastUpdated);
|
||||
|
||||
updateAvaliableResult = pythonUpdateAvaliableResult.cast<bool>();
|
||||
});
|
||||
|
||||
return result && updateAvaliableResult;
|
||||
}
|
||||
|
||||
AZStd::pair<AZStd::string, AZStd::string> PythonBindings::GetSimpleDetailedErrorPair()
|
||||
{
|
||||
AZStd::string detailedString = m_pythonErrorStrings.size() == 1
|
||||
? ""
|
||||
: AZStd::accumulate(m_pythonErrorStrings.begin(), m_pythonErrorStrings.end(), AZStd::string(""));
|
||||
|
||||
return AZStd::pair<AZStd::string, AZStd::string>(m_pythonErrorStrings.front(), detailedString);
|
||||
}
|
||||
|
||||
void PythonBindings::AddErrorString(AZStd::string errorString)
|
||||
{
|
||||
m_pythonErrorStrings.push_back(errorString);
|
||||
}
|
||||
|
||||
void PythonBindings::ClearErrorStrings()
|
||||
{
|
||||
m_pythonErrorStrings.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ namespace O3DE::ProjectManager
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemInfos(const QString& projectPath) override;
|
||||
AZ::Outcome<QVector<AZStd::string>, AZStd::string> GetEnabledGemNames(const QString& projectPath) override;
|
||||
AZ::Outcome<void, AZStd::string> RegisterGem(const QString& gemPath, const QString& projectPath = {}) override;
|
||||
AZ::Outcome<void, AZStd::string> UnregisterGem(const QString& gemPath, const QString& projectPath = {}) override;
|
||||
|
||||
// Project
|
||||
AZ::Outcome<ProjectInfo> CreateProject(const QString& projectTemplatePath, const ProjectInfo& projectInfo) override;
|
||||
@@ -61,12 +62,18 @@ namespace O3DE::ProjectManager
|
||||
// Gem Repos
|
||||
AZ::Outcome<void, AZStd::string> RefreshGemRepo(const QString& repoUri) override;
|
||||
bool RefreshAllGemRepos() override;
|
||||
bool AddGemRepo(const QString& repoUri) override;
|
||||
AZ::Outcome<void, AZStd::pair<AZStd::string, AZStd::string>> AddGemRepo(const QString& repoUri) override;
|
||||
bool RemoveGemRepo(const QString& repoUri) override;
|
||||
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() override;
|
||||
AZ::Outcome<void, AZStd::string> DownloadGem(const QString& gemName, std::function<void(int)> gemProgressCallback) override;
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetGemInfosForRepo(const QString& repoUri) override;
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetGemInfosForAllRepos() override;
|
||||
AZ::Outcome<void, AZStd::pair<AZStd::string, AZStd::string>> DownloadGem(
|
||||
const QString& gemName, std::function<void(int, int)> gemProgressCallback, bool force = false) override;
|
||||
void CancelDownload() override;
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemRepoGemsInfos() override;
|
||||
bool IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated) override;
|
||||
|
||||
void AddErrorString(AZStd::string errorString) override;
|
||||
void ClearErrorStrings() override;
|
||||
|
||||
private:
|
||||
AZ_DISABLE_COPY_MOVE(PythonBindings);
|
||||
@@ -77,8 +84,10 @@ namespace O3DE::ProjectManager
|
||||
GemRepoInfo GetGemRepoInfo(pybind11::handle repoUri);
|
||||
ProjectInfo ProjectInfoFromPath(pybind11::handle path);
|
||||
ProjectTemplateInfo ProjectTemplateInfoFromPath(pybind11::handle path, pybind11::handle pyProjectPath);
|
||||
AZ::Outcome<void, AZStd::string> GemRegistration(const QString& gemPath, const QString& projectPath, bool remove = false);
|
||||
bool RegisterThisEngine();
|
||||
bool StopPython();
|
||||
AZStd::pair<AZStd::string, AZStd::string> GetSimpleDetailedErrorPair();
|
||||
|
||||
|
||||
bool m_pythonStarted = false;
|
||||
@@ -98,5 +107,6 @@ namespace O3DE::ProjectManager
|
||||
pybind11::handle m_pathlib;
|
||||
|
||||
bool m_requestCancelDownload = false;
|
||||
AZStd::vector<AZStd::string> m_pythonErrorStrings;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -94,11 +94,19 @@ namespace O3DE::ProjectManager
|
||||
/**
|
||||
* Registers the gem to the specified project, or to the o3de_manifest.json if no project path is given
|
||||
* @param gemPath the path to the gem
|
||||
* @param projectPath the path to the project. If empty, will register the external path in o3de_manifest.json
|
||||
* @param projectPath the path to the project. If empty, will register the external path in o3de_manifest.json
|
||||
* @return An outcome with the success flag as well as an error message in case of a failure.
|
||||
*/
|
||||
virtual AZ::Outcome<void, AZStd::string> RegisterGem(const QString& gemPath, const QString& projectPath = {}) = 0;
|
||||
|
||||
/**
|
||||
* Unregisters the gem from the specified project, or from the o3de_manifest.json if no project path is given
|
||||
* @param gemPath the path to the gem
|
||||
* @param projectPath the path to the project. If empty, will unregister the external path in o3de_manifest.json
|
||||
* @return An outcome with the success flag as well as an error message in case of a failure.
|
||||
*/
|
||||
virtual AZ::Outcome<void, AZStd::string> UnregisterGem(const QString& gemPath, const QString& projectPath = {}) = 0;
|
||||
|
||||
|
||||
// Projects
|
||||
|
||||
@@ -192,9 +200,9 @@ namespace O3DE::ProjectManager
|
||||
/**
|
||||
* Registers this gem repo with the current engine.
|
||||
* @param repoUri the absolute filesystem path or url to the gem repo.
|
||||
* @return true on success, false on failure.
|
||||
* @return an outcome with a pair of string error and detailed messages on failure.
|
||||
*/
|
||||
virtual bool AddGemRepo(const QString& repoUri) = 0;
|
||||
virtual AZ::Outcome<void, AZStd::pair<AZStd::string, AZStd::string>> AddGemRepo(const QString& repoUri) = 0;
|
||||
|
||||
/**
|
||||
* Unregisters this gem repo with the current engine.
|
||||
@@ -210,23 +218,51 @@ namespace O3DE::ProjectManager
|
||||
virtual AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() = 0;
|
||||
|
||||
/**
|
||||
* Downloads and registers a Gem.
|
||||
* @param gemName the name of the Gem to download
|
||||
* @param gemProgressCallback a callback function that is called with an int percentage download value
|
||||
* @return an outcome with a string error message on failure.
|
||||
* Gathers all gem infos from the provided repo
|
||||
* @param repoUri the absolute filesystem path or url to the gem repo.
|
||||
* @return A list of gem infos.
|
||||
*/
|
||||
virtual AZ::Outcome<void, AZStd::string> DownloadGem(const QString& gemName, std::function<void(int)> gemProgressCallback) = 0;
|
||||
|
||||
/**
|
||||
* Cancels the current download.
|
||||
*/
|
||||
virtual void CancelDownload() = 0;
|
||||
virtual AZ::Outcome<QVector<GemInfo>, AZStd::string> GetGemInfosForRepo(const QString& repoUri) = 0;
|
||||
|
||||
/**
|
||||
* Gathers all gem infos for all gems registered from repos.
|
||||
* @return A list of gem infos.
|
||||
*/
|
||||
virtual AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemRepoGemsInfos() = 0;
|
||||
virtual AZ::Outcome<QVector<GemInfo>, AZStd::string> GetGemInfosForAllRepos() = 0;
|
||||
|
||||
/**
|
||||
* Downloads and registers a Gem.
|
||||
* @param gemName the name of the Gem to download.
|
||||
* @param gemProgressCallback a callback function that is called with an int percentage download value.
|
||||
* @param force should we forcibly overwrite the old version of the gem.
|
||||
* @return an outcome with a pair of string error and detailed messages on failure.
|
||||
*/
|
||||
virtual AZ::Outcome<void, AZStd::pair<AZStd::string, AZStd::string>> DownloadGem(
|
||||
const QString& gemName, std::function<void(int, int)> gemProgressCallback, bool force = false) = 0;
|
||||
|
||||
/**
|
||||
* Cancels the current download.
|
||||
*/
|
||||
virtual void CancelDownload() = 0;
|
||||
|
||||
/**
|
||||
* Checks if there is an update avaliable for a gem on a repo.
|
||||
* @param gemName the name of the gem to check.
|
||||
* @param lastUpdated last time the gem was update.
|
||||
* @return true if update is avaliable, false if not.
|
||||
*/
|
||||
virtual bool IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated) = 0;
|
||||
|
||||
/**
|
||||
* Add an error string to be returned when the current python call is complete.
|
||||
* @param The error string to be displayed.
|
||||
*/
|
||||
virtual void AddErrorString(AZStd::string errorString) = 0;
|
||||
|
||||
/**
|
||||
* Clears the current list of error strings.
|
||||
*/
|
||||
virtual void ClearErrorStrings() = 0;
|
||||
};
|
||||
|
||||
using PythonBindingsInterface = AZ::Interface<IPythonBindings>;
|
||||
|
||||
@@ -47,9 +47,9 @@ namespace O3DE::ProjectManager
|
||||
return tr("Missing");
|
||||
}
|
||||
|
||||
virtual bool ContainsScreen([[maybe_unused]] ProjectManagerScreen screen)
|
||||
virtual bool ContainsScreen(ProjectManagerScreen screen)
|
||||
{
|
||||
return false;
|
||||
return GetScreenEnum() == screen;
|
||||
}
|
||||
virtual void GoToScreen([[maybe_unused]] ProjectManagerScreen screen)
|
||||
{
|
||||
@@ -58,7 +58,6 @@ namespace O3DE::ProjectManager
|
||||
//! Notify this screen it is the current screen
|
||||
virtual void NotifyCurrentScreen()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
signals:
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
#include <UpdateProjectCtrl.h>
|
||||
#include <UpdateProjectSettingsScreen.h>
|
||||
#include <ProjectUtils.h>
|
||||
#include <DownloadController.h>
|
||||
#include <ProjectManagerSettings.h>
|
||||
#include <AzCore/Settings/SettingsRegistry.h>
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QMessageBox>
|
||||
@@ -94,6 +97,17 @@ namespace O3DE::ProjectManager
|
||||
return ProjectManagerScreen::UpdateProject;
|
||||
}
|
||||
|
||||
bool UpdateProjectCtrl::ContainsScreen(ProjectManagerScreen screen)
|
||||
{
|
||||
// Do not include GemRepos because we don't want to advertise jumping to it from all other screens here
|
||||
return screen == GetScreenEnum() || screen == ProjectManagerScreen::GemCatalog;
|
||||
}
|
||||
|
||||
void UpdateProjectCtrl::GoToScreen(ProjectManagerScreen screen)
|
||||
{
|
||||
OnChangeScreenRequest(screen);
|
||||
}
|
||||
|
||||
// Called when pressing "Edit Project Settings..."
|
||||
void UpdateProjectCtrl::NotifyCurrentScreen()
|
||||
{
|
||||
@@ -114,6 +128,16 @@ namespace O3DE::ProjectManager
|
||||
m_stack->setCurrentWidget(m_gemRepoScreen);
|
||||
Update();
|
||||
}
|
||||
else if (screen == ProjectManagerScreen::GemCatalog)
|
||||
{
|
||||
m_stack->setCurrentWidget(m_gemCatalogScreen);
|
||||
Update();
|
||||
}
|
||||
else if (screen == ProjectManagerScreen::UpdateProjectSettings)
|
||||
{
|
||||
m_stack->setCurrentWidget(m_updateSettingsScreen);
|
||||
Update();
|
||||
}
|
||||
else
|
||||
{
|
||||
emit ChangeScreenRequest(screen);
|
||||
@@ -280,6 +304,21 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
if (newProjectSettings.m_projectName != m_projectInfo.m_projectName)
|
||||
{
|
||||
// update reg key
|
||||
QString oldSettingsKey = GetProjectBuiltSuccessfullyKey(m_projectInfo.m_projectName);
|
||||
QString newSettingsKey = GetProjectBuiltSuccessfullyKey(newProjectSettings.m_projectName);
|
||||
|
||||
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
bool projectBuiltSuccessfully = false;
|
||||
if (settingsRegistry && settingsRegistry->Get(projectBuiltSuccessfully, oldSettingsKey.toStdString().c_str()))
|
||||
{
|
||||
settingsRegistry->Set(newSettingsKey.toStdString().c_str(), projectBuiltSuccessfully);
|
||||
SaveProjectManagerSettings();
|
||||
}
|
||||
}
|
||||
|
||||
if (!newProjectSettings.m_newPreviewImagePath.isEmpty())
|
||||
{
|
||||
if (!ProjectUtils::ReplaceProjectFile(
|
||||
|
||||
@@ -24,7 +24,8 @@ namespace O3DE::ProjectManager
|
||||
QT_FORWARD_DECLARE_CLASS(GemCatalogScreen)
|
||||
QT_FORWARD_DECLARE_CLASS(GemRepoScreen)
|
||||
|
||||
class UpdateProjectCtrl : public ScreenWidget
|
||||
class UpdateProjectCtrl
|
||||
: public ScreenWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
@@ -32,7 +33,8 @@ namespace O3DE::ProjectManager
|
||||
~UpdateProjectCtrl() = default;
|
||||
ProjectManagerScreen GetScreenEnum() override;
|
||||
|
||||
protected:
|
||||
bool ContainsScreen(ProjectManagerScreen screen) override;
|
||||
void GoToScreen(ProjectManagerScreen screen) override;
|
||||
void NotifyCurrentScreen() override;
|
||||
|
||||
protected slots:
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace O3DE::ProjectManager
|
||||
previewExtrasLayout->setContentsMargins(50, 0, 0, 0);
|
||||
|
||||
QLabel* projectPreviewLabel = new QLabel(tr("Select an image (PNG). Minimum %1 x %2 pixels.")
|
||||
.arg(QString::number(ProjectPreviewImageWidth), QString::number(ProjectPreviewImageHeight)));
|
||||
.arg(QString::number(ProjectPreviewImageWidth), QString::number(ProjectPreviewImageHeight)));
|
||||
projectPreviewLabel->setObjectName("projectPreviewLabel");
|
||||
previewExtrasLayout->addWidget(projectPreviewLabel);
|
||||
|
||||
|
||||
@@ -58,6 +58,8 @@ set(FILES
|
||||
Source/CreateProjectCtrl.cpp
|
||||
Source/UpdateProjectCtrl.h
|
||||
Source/UpdateProjectCtrl.cpp
|
||||
Source/ProjectManagerSettings.h
|
||||
Source/ProjectManagerSettings.cpp
|
||||
Source/ProjectsScreen.h
|
||||
Source/ProjectsScreen.cpp
|
||||
Source/ProjectSettingsScreen.h
|
||||
@@ -96,6 +98,10 @@ set(FILES
|
||||
Source/GemCatalog/GemListHeaderWidget.cpp
|
||||
Source/GemCatalog/GemModel.h
|
||||
Source/GemCatalog/GemModel.cpp
|
||||
Source/GemCatalog/GemUninstallDialog.h
|
||||
Source/GemCatalog/GemUninstallDialog.cpp
|
||||
Source/GemCatalog/GemUpdateDialog.h
|
||||
Source/GemCatalog/GemUpdateDialog.cpp
|
||||
Source/GemCatalog/GemDependenciesDialog.h
|
||||
Source/GemCatalog/GemDependenciesDialog.cpp
|
||||
Source/GemCatalog/GemRequirementDialog.h
|
||||
|
||||
@@ -39,7 +39,6 @@ namespace PythonBindingsExample
|
||||
AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusConnect();
|
||||
|
||||
// prepare the Python binding gem(s)
|
||||
CalculateExecutablePath();
|
||||
Start(Descriptor());
|
||||
|
||||
AZ::SerializeContext* context;
|
||||
|
||||
@@ -368,7 +368,6 @@ namespace AZ::SceneAPI::Containers
|
||||
MOCK_METHOD0(GetSerializeContext, AZ::SerializeContext* ());
|
||||
MOCK_METHOD0(GetJsonRegistrationContext, AZ::JsonRegistrationContext* ());
|
||||
MOCK_METHOD0(GetBehaviorContext, AZ::BehaviorContext* ());
|
||||
MOCK_CONST_METHOD0(GetAppRoot, const char*());
|
||||
MOCK_CONST_METHOD0(GetEngineRoot, const char*());
|
||||
MOCK_CONST_METHOD0(GetExecutableFolder, const char* ());
|
||||
MOCK_CONST_METHOD1(QueryApplicationType, void(AZ::ApplicationTypeQuery&));
|
||||
|
||||
@@ -202,8 +202,6 @@ namespace AZ
|
||||
bool skipSystem = commandLine->HasSwitch("skipsystem");
|
||||
bool isDryRun = commandLine->HasSwitch("dryrun");
|
||||
|
||||
const char* appRoot = const_cast<const Application&>(application).GetAppRoot();
|
||||
|
||||
PathDocumentContainer documents;
|
||||
bool result = true;
|
||||
const AZStd::string& filePath = application.GetConfigFilePath();
|
||||
@@ -230,7 +228,7 @@ namespace AZ
|
||||
}
|
||||
|
||||
auto callback =
|
||||
[&result, skipGems, skipSystem, &configurationName, sourceGameFolder, &appRoot, &documents, &convertSettings, &verifySettings]
|
||||
[&result, skipGems, skipSystem, &configurationName, sourceGameFolder, &documents, &convertSettings, &verifySettings]
|
||||
(void* classPtr, const Uuid& classId, SerializeContext* context)
|
||||
{
|
||||
if (classId == azrtti_typeid<AZ::ComponentApplication::Descriptor>())
|
||||
@@ -238,7 +236,7 @@ namespace AZ
|
||||
if (!skipSystem)
|
||||
{
|
||||
result = ConvertSystemSettings(documents, *reinterpret_cast<AZ::ComponentApplication::Descriptor*>(classPtr),
|
||||
configurationName, sourceGameFolder, appRoot) && result;
|
||||
configurationName, sourceGameFolder) && result;
|
||||
}
|
||||
|
||||
// Cleanup the Serialized Element to allow any classes within the element's hierarchy to delete
|
||||
@@ -443,7 +441,7 @@ namespace AZ
|
||||
}
|
||||
|
||||
bool Converter::ConvertSystemSettings(PathDocumentContainer& documents, const ComponentApplication::Descriptor& descriptor,
|
||||
const AZStd::string& configurationName, const AZ::IO::PathView& projectFolder, [[maybe_unused]] const AZStd::string& applicationRoot)
|
||||
const AZStd::string& configurationName, const AZ::IO::PathView& projectFolder)
|
||||
{
|
||||
AZ::IO::FixedMaxPath memoryFilePath{ projectFolder };
|
||||
memoryFilePath /= "Registry";
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace AZ
|
||||
using PathDocumentContainer = AZStd::vector<PathDocumentPair>;
|
||||
|
||||
static bool ConvertSystemSettings(PathDocumentContainer& documents, const ComponentApplication::Descriptor& descriptor,
|
||||
const AZStd::string& configurationName, const AZ::IO::PathView& projectFolder, const AZStd::string& applicationRoot);
|
||||
const AZStd::string& configurationName, const AZ::IO::PathView& projectFolder);
|
||||
static bool ConvertSystemComponents(PathDocumentContainer& documents, const Entity& entity,
|
||||
const AZStd::string& configurationName, const AZ::IO::PathView& projectFolder,
|
||||
const JsonSerializerSettings& convertSettings, const JsonDeserializerSettings& verifySettings);
|
||||
|
||||
Reference in New Issue
Block a user