Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion docs/user_guide/setup-submitter.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,4 +203,23 @@ This example will use the Meerkat Demo from the Unreal Marketplace:
1. Note: Unreal Engine version autodetection is coming in a future release

1. Ready to Go! Hit "Render (Remote)".
1. You can go to Deadline Cloud Monitor and watch the progress of your job.
1. You can go to Deadline Cloud Monitor and watch the progress of your job.


# Update Notifications

The submitter plugin automatically checks for newer releases on GitHub when Unreal Editor starts. If an update is available, a dialog will prompt you to visit the release page.

To deactivate update notifications, uncheck "Show submitter update notifications" under "General Settings" in the Deadline Cloud settings panel (Edit > Project Settings > Plugins > Deadline Cloud).

Alternatively, you can use the CLI:

```
deadline config set settings.submitter_update_notification false
```

To re-enable:

```
deadline config set settings.submitter_update_notification true
```
4 changes: 4 additions & 0 deletions src/unreal_plugin/Content/Python/init_unreal.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ def __init__(

logger.info("INIT DEADLINE CLOUD")

from update_check import safe_check_and_show_update_dialog

safe_check_and_show_update_dialog()

logger.info(f'DEADLINE CLOUD PATH: {os.getenv("DEADLINE_CLOUD")}')

# These unused imports are REQUIRED!!!
Expand Down
7 changes: 7 additions & 0 deletions src/unreal_plugin/Content/Python/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,13 @@ def save_to_aws_config(
config=config_parser,
)

# general.show_update_notifications (settings.submitter_update_notification)
config.set_setting(
"settings.submitter_update_notification",
"true" if settings.general.show_update_notifications else "false",
config=config_parser,
)

config_file.write_config(config_parser)

if farm_queue_update:
Expand Down
167 changes: 167 additions & 0 deletions src/unreal_plugin/Content/Python/update_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

"""
Update checker for the Deadline Cloud for Unreal Engine plugin.

Checks the GitHub releases API for a newer version and shows an Unreal
Editor dialog when an update is available. Respects the Deadline Cloud
``settings.submitter_update_notification`` config toggle.
"""

from __future__ import annotations

import json
import logging
import os
import socket
import ssl
import urllib.request
import urllib.error
import webbrowser

import botocore
import unreal

from packaging.version import Version, InvalidVersion

from deadline.client.config import config_file
from deadline.unreal_submitter._version import version as _current_version

logger = logging.getLogger(__name__)

GITHUB_LATEST_RELEASE_URL = (
"https://api.github.com/repos/aws-deadline/deadline-cloud-for-unreal-engine/releases/latest"
)
RELEASES_PAGE_URL = "https://github.com/aws-deadline/deadline-cloud-for-unreal-engine/releases"
SETUP_GUIDE_URL = "https://aws-deadline.github.io/unreal-engine/setup-submitter/"
_REQUEST_TIMEOUT_SECONDS = 5


def _is_update_notification_enabled() -> bool:
"""Check whether the user has opted in to update notifications."""
return config_file.str2bool(config_file.get_setting("settings.submitter_update_notification"))


def _get_current_version() -> str:
"""Return the currently installed plugin version string."""
return _current_version


def _fetch_latest_version() -> str | None:
"""Fetch the latest release tag from GitHub.

Returns:
The version string (e.g. ``"0.6.5"``) or ``None`` on failure.
"""
# Pin to GitHub REST API v3 JSON format so the response shape stays stable.
req = urllib.request.Request(
GITHUB_LATEST_RELEASE_URL,
headers={"Accept": "application/vnd.github.v3+json"},
)

# Build a strict TLS context: enforce TLS 1.2+ and use the botocore CA
# bundle so certificate verification works even in embedded Python
# environments (e.g. Unreal) that may lack system root certificates.
ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
ctx.minimum_version = ssl.TLSVersion.TLSv1_2
ctx.load_verify_locations(_get_botocore_ca_bundle())

try:
with urllib.request.urlopen(req, timeout=_REQUEST_TIMEOUT_SECONDS, context=ctx) as resp:
data = json.loads(resp.read().decode("utf-8"))
tag = data.get("tag_name", "")
return tag.lstrip("v") if tag else None
except urllib.error.URLError:
return None
except (socket.timeout, TimeoutError):
return None
except json.JSONDecodeError:
return None


def _get_botocore_ca_bundle() -> str:
"""Return the path to botocore's bundled CA certificate bundle."""
return os.path.join(os.path.dirname(botocore.__file__), "cacert.pem")


def _is_update_available(current: str, latest: str) -> bool:
"""Return True if *latest* is strictly newer than *current*."""
try:
return Version(latest) > Version(current)
except InvalidVersion:
return False


def safe_check_and_show_update_dialog() -> bool:
"""Check GitHub for a newer release and show an Unreal dialog if found.

Returns:
``True`` if the user chose to open the download page (caller may
want to skip opening the submitter), ``False`` otherwise.
"""
try:
return _check_and_show_update_dialog()
except Exception:
logger.debug("Update check failed -- skipping", exc_info=True)
return False


def _check_and_show_update_dialog() -> bool:
"""Internal implementation of the update check and dialog flow."""
if not _is_update_notification_enabled():
return False

current_version = _get_current_version()

latest_version = _fetch_latest_version()

if not latest_version:
return False

if not _is_update_available(current_version, latest_version):
return False

message = (
f"Version {latest_version} of Deadline Cloud for Unreal Engine "
f"submitter is now available.\n\n"
f"Current: {current_version} -> New: {latest_version}\n\n"
f"View release notes:\n{RELEASES_PAGE_URL}\n\n"
"To disable these notifications, go to Edit > Project Settings > "
'search for "Deadline" and uncheck "Show Submitter Update '
'Notifications" under General Settings.\n\n'
"Click 'Yes' to open the release page, or 'No' to dismiss."
)

response = unreal.EditorDialog.show_message(
"New version available",
message,
unreal.AppMsgType.YES_NO,
)

if response == unreal.AppReturnType.YES:
try:
webbrowser.open(RELEASES_PAGE_URL)
except Exception:
return False

guide_message = (
"Please follow the setup guide to install the new release and "
"then restart Unreal Engine to use the new version.\n\n"
f"Setup guide:\n{SETUP_GUIDE_URL}\n\n"
"Click 'Yes' to open the setup guide, or 'No' to dismiss."
)

guide_response = unreal.EditorDialog.show_message(
"Installation Guide",
guide_message,
unreal.AppMsgType.YES_NO,
)

if guide_response == unreal.AppReturnType.YES:
try:
webbrowser.open(SETUP_GUIDE_URL)
except Exception:
logger.debug("Failed to open setup guide URL", exc_info=True)
return True

return False
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace DeadlineSettingsKeys
const FString AutoAccept = TEXT("settings.auto_accept");
const FString ConflictResolution = TEXT("settings.conflict_resolution");
const FString LogLevel = TEXT("settings.log_level");
const FString SubmitterUpdateNotification = TEXT("settings.submitter_update_notification");
}

UDeadlineCloudDeveloperSettings::UDeadlineCloudDeveloperSettings()
Expand Down Expand Up @@ -329,6 +330,9 @@ void UDeadlineCloudDeveloperSettings::RefreshFromDefaultProfileInternal()

FString CurrentLoggingLevel = Library->GetAWSStringConfigSetting(DeadlineSettingsKeys::LogLevel);
WorkStationConfiguration.General.CurrentLoggingLevel = CurrentLoggingLevel;

FString SubmitterUpdateNotification = Library->GetAWSStringConfigSetting(DeadlineSettingsKeys::SubmitterUpdateNotification);
WorkStationConfiguration.General.ShowUpdateNotifications = SubmitterUpdateNotification != TEXT("false");
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

#pragma once
#include "Misc/AutomationTest.h"
#include "CoreMinimal.h"
#include "Engine/Engine.h"
#include "UObject/UObjectGlobals.h"
#include "DeadlineCloudJobSettings/DeadlineCloudDeveloperSettings.h"
#include "PythonAPILibraries/DeadlineCloudSettingsLibrary.h"

// ---------------------------------------------------------------------------
// Spec tests for the update dialog flow.
//
// The update-notification dialog itself is driven by Python
// (update_check.py -> unreal.EditorDialog.show_message) and is covered by
// the Python unit tests in test_update_check.py.
//
// These C++ automation tests verify the settings-layer contract that the
// Python code depends on:
// - The ShowUpdateNotifications property defaults to true.
// - The config key "settings.submitter_update_notification" is readable
// and returns a valid value via the settings library.
// - Toggling the setting via the UI persists through SaveToFile and
// is correctly restored by RefreshFromDefaultProfileInternal.
// ---------------------------------------------------------------------------

// ---------------------------------------------------------------------------
// 1. Default value: a fresh struct should have notifications enabled
// ---------------------------------------------------------------------------
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FUpdateDialog_SettingDefaultIsTrue,
"DeadlineCloud.UpdateDialog.Setting.DefaultIsTrue",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)

bool FUpdateDialog_SettingDefaultIsTrue::RunTest(const FString& Parameters)
{
FDeadlineCloudGeneralPluginSettings DefaultGeneral;
TestTrue(
TEXT("ShowUpdateNotifications should default to true on a fresh struct"),
DefaultGeneral.ShowUpdateNotifications);

return true;
}

// ---------------------------------------------------------------------------
// 2. Save-and-reload round-trip through the config file
// Toggles the setting off, saves to the Deadline Cloud config file via
// SaveToFile(), reloads via RefreshFromDefaultProfileInternal(), and
// verifies the bool was correctly persisted as the string "false" and
// mapped back to false. Then does the same for true. Restores original.
// ---------------------------------------------------------------------------
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FUpdateDialog_SettingSaveAndReloadRoundTrip,
"DeadlineCloud.UpdateDialog.Setting.SaveAndReloadRoundTrip",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)

bool FUpdateDialog_SettingSaveAndReloadRoundTrip::RunTest(const FString& Parameters)
{
UDeadlineCloudDeveloperSettings* Settings = UDeadlineCloudDeveloperSettings::GetMutable();
TestNotNull(TEXT("DeveloperSettings singleton must exist"), Settings);
if (!Settings) return false;

UDeadlineCloudSettingsLibrary* Library = UDeadlineCloudSettingsLibrary::Get();
if (!Library)
{
AddWarning(TEXT("DeadlineCloudSettingsLibrary not available (Python not initialized). Skipping round-trip test."));
return true;
}

const bool bOriginal = Settings->WorkStationConfiguration.General.ShowUpdateNotifications;
const FString ConfigKey = TEXT("settings.submitter_update_notification");

// --- Toggle OFF, save, reload, verify ---
Settings->WorkStationConfiguration.General.ShowUpdateNotifications = false;
Settings->SaveToFile();

FString SavedValue = Library->GetAWSStringConfigSetting(ConfigKey);
TestTrue(
TEXT("Config should contain 'false' after saving with notifications off"),
SavedValue.Equals(TEXT("false"), ESearchCase::IgnoreCase));

Settings->RefreshFromDefaultProfileInternal();
TestFalse(
TEXT("ShowUpdateNotifications should be false after reload"),
Settings->WorkStationConfiguration.General.ShowUpdateNotifications);

// --- Toggle ON, save, reload, verify ---
Settings->WorkStationConfiguration.General.ShowUpdateNotifications = true;
Settings->SaveToFile();

SavedValue = Library->GetAWSStringConfigSetting(ConfigKey);
TestTrue(
TEXT("Config should contain 'true' after saving with notifications on"),
SavedValue.Equals(TEXT("true"), ESearchCase::IgnoreCase));

Settings->RefreshFromDefaultProfileInternal();
TestTrue(
TEXT("ShowUpdateNotifications should be true after reload"),
Settings->WorkStationConfiguration.General.ShowUpdateNotifications);

// --- Restore original ---
Settings->WorkStationConfiguration.General.ShowUpdateNotifications = bOriginal;
Settings->SaveToFile();

return true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ struct UNREALDEADLINECLOUDSERVICE_API FDeadlineCloudGeneralPluginSettings
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(GetOptions="GetLoggingLevels", DisplayPriority=8, Category="General Settings"))
FString CurrentLoggingLevel;

/**
* Whether to show update notifications when a newer version of the plugin is available on GitHub.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(DisplayName="Show submitter update notifications", DisplayPriority=9, Category="General Settings"))
bool ShowUpdateNotifications = true;

};

/**
Expand Down
Loading
Loading