WuhuIslandTesting/Library/PackageCache/com.unity.addressables@1.21.12/Editor/Build/CcdBuildEvents.cs

913 lines
36 KiB
C#
Raw Normal View History

2025-01-07 02:06:59 +01:00
#if ENABLE_CCD
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Unity.Services.Ccd.Management;
using Unity.Services.Ccd.Management.Models;
using Unity.Services.Core;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.AddressableAssets.Settings.GroupSchemas;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.AddressableAssets.ResourceLocators;
namespace UnityEditor.AddressableAssets.Build
{
/// <summary>
/// CCD Events used for building Addressables content with CCD.
/// </summary>
public class CcdBuildEvents
{
static CcdBuildEvents s_Instance;
/// <summary>
/// The static instance of CcdBuildEvents.
/// </summary>
public static CcdBuildEvents Instance
{
get
{
if (s_Instance == null)
{
s_Instance = new CcdBuildEvents();
s_Instance.RegisterNewBuildEvents();
s_Instance.RegisterUpdateBuildEvents();
}
return s_Instance;
}
}
const string k_ContentStatePath = "addressables_content_state.bin";
internal void RegisterNewBuildEvents()
{
OnPreBuildEvents += s_Instance.VerifyBuildVersion;
OnPreBuildEvents += s_Instance.RefreshDataSources;
OnPreBuildEvents += s_Instance.VerifyTargetBucket;
OnPostBuildEvents += s_Instance.UploadContentState;
OnPostBuildEvents += s_Instance.UploadAndRelease;
}
internal void RegisterUpdateBuildEvents()
{
OnPreUpdateEvents += s_Instance.VerifyBuildVersion;
OnPreUpdateEvents += s_Instance.RefreshDataSources;
OnPreUpdateEvents += s_Instance.DownloadContentStateBin;
OnPreUpdateEvents += s_Instance.VerifyTargetBucket;
OnPostUpdateEvents += s_Instance.UploadContentState;
OnPostUpdateEvents += s_Instance.UploadAndRelease;
}
/// <summary>
/// Pre Addressables build event.
/// </summary>
public delegate Task<bool> PreEvent(AddressablesDataBuilderInput input);
/// <summary>
/// Pre new build events.
/// Default events:
/// <see cref="RefreshDataSources"/>
/// <see cref="VerifyTargetBucket"/>
/// </summary>
public static event PreEvent OnPreBuildEvents;
/// <summary>
/// Pre update build events.
/// Default events:
/// <see cref="RefreshDataSources"/>
/// <see cref="DownloadContentStateBin"/>
/// <see cref="VerifyTargetBucket"/>
/// </summary>
public static event PreEvent OnPreUpdateEvents;
/// <summary>
/// Post Addressables build event.
/// </summary>
public delegate Task<bool> PostEvent(AddressablesDataBuilderInput input,
AddressablesPlayerBuildResult result);
/// <summary>
/// Post new build events.
/// Default events:
/// <see cref="UploadContentState"/>
/// <see cref="UploadAndRelease"/>
/// </summary>
public static event PostEvent OnPostBuildEvents;
/// <summary>
/// Post update build events.
/// Default events:
/// <see cref="UploadContentState"/>
/// <see cref="UploadAndRelease"/>
/// </summary>
public static event PostEvent OnPostUpdateEvents;
internal async Task<bool> OnPreEvent(bool isUpdate, AddressablesDataBuilderInput input)
{
if (isUpdate)
{
return await InvokePreEvent(OnPreUpdateEvents, input);
}
return await InvokePreEvent(OnPreBuildEvents, input);
}
internal async Task<bool> InvokePreEvent(PreEvent events, AddressablesDataBuilderInput input)
{
if (events == null)
{
return true;
}
var total = events.GetInvocationList().Length;
for (var i = 0; i < total; i++)
{
var e = (PreEvent)events.GetInvocationList()[i];
var shouldContinue = await e.Invoke(input);
if (!shouldContinue)
{
return false;
}
}
return true;
}
internal async Task<bool> OnPostEvent(bool isUpdate, AddressablesDataBuilderInput input,
AddressablesPlayerBuildResult result)
{
if (isUpdate)
{
return await InvokePostEvent(OnPostUpdateEvents, input, result);
}
return await InvokePostEvent(OnPostBuildEvents, input, result);
}
internal async Task<bool> InvokePostEvent(PostEvent events, AddressablesDataBuilderInput input,
AddressablesPlayerBuildResult result)
{
if (events == null)
return true;
var total = events.GetInvocationList().Length;
for (var i = 0; i < total; i++)
{
var e = (PostEvent)events.GetInvocationList()[i];
var shouldContinue = await e.Invoke(input, result);
if (!shouldContinue)
{
// if a post-build step adds an error we have to log it manually
if (result != null && result.Error != "")
{
Debug.LogError(result.Error);
}
return false;
}
}
return true;
}
/// <summary>
/// Prepend an event to the pre new build events.
/// </summary>
/// <param name="newEvent">Pre build event</param>
public static void PrependPreBuildEvent(PreEvent newEvent)
{
Delegate[] oldEvents = OnPreBuildEvents?.GetInvocationList();
OnPreBuildEvents = newEvent;
if (oldEvents != null)
foreach (var t in oldEvents)
{
OnPreBuildEvents += (PreEvent)(t);
}
}
/// <summary>
/// Prepend an event to the post new build events.
/// </summary>
/// <param name="newEvent">Post build event</param>
public static void PrependPostBuildEvent(PostEvent newEvent)
{
Delegate[] oldEvents = OnPostBuildEvents?.GetInvocationList();
OnPostBuildEvents = newEvent;
if (oldEvents != null)
foreach (var t in oldEvents)
{
OnPostBuildEvents += (PostEvent)(t);
}
}
/// <summary>
/// Prepend an event to the pre update build events.
/// </summary>
/// <param name="newEvent">Pre build event</param>
public static void PrependPreUpdateEvent(PreEvent newEvent)
{
Delegate[] oldEvents = OnPreUpdateEvents?.GetInvocationList();
OnPreUpdateEvents = newEvent;
if (oldEvents != null)
foreach (var t in oldEvents)
{
OnPreUpdateEvents += (PreEvent)(t);
}
}
/// <summary>
/// Prepend an event to the post update build events.
/// </summary>
/// <param name="newEvent">Post build event</param>
public static void PrependPostUpdateEvent(PostEvent newEvent)
{
Delegate[] oldEvents = OnPostUpdateEvents?.GetInvocationList();
OnPostUpdateEvents = newEvent;
if (oldEvents != null)
foreach (var t in oldEvents)
{
OnPostUpdateEvents += (PostEvent)(t);
}
}
public async Task<bool> VerifyBuildVersion(AddressablesDataBuilderInput input)
{
if (string.IsNullOrWhiteSpace(input.AddressableSettings.OverridePlayerVersion))
{
Addressables.LogWarning("<b>When using CCD it is recommended that you set a 'Player Version Override' in Addressables Settings.</b> You can have it use the Player build version by setting it to [UnityEditor.PlayerSettings.bundleVersion].");
Addressables.LogWarning("Documentation on how to disable this warning is available in the example DisableBuildWarnings.cs.");
}
return true;
}
/// <summary>
/// Update the CCD data source settings.
/// </summary>
/// <param name="input">Addressables data builder context</param>
/// <returns></returns>
public async Task<bool> RefreshDataSources(AddressablesDataBuilderInput input)
{
try
{
var projectId = CloudProjectSettings.projectId;
await ProfileDataSourceSettings.UpdateCCDDataSourcesAsync(projectId, true);
}
catch (Exception e)
{
Debug.LogError(e.ToString());
return false;
}
return true;
}
internal async Task<bool> LoopGroups(AddressableAssetSettings settings, Func<AddressableAssetSettings, AddressableAssetGroup, BundledAssetGroupSchema, Task<bool>> action)
{
var tasks = new List<Task<bool>>();
foreach (var group in settings.groups)
{
if (group == null)
{
continue;
}
var schema = group.GetSchema<BundledAssetGroupSchema>();
if (schema == null)
{
continue;
}
tasks.Add(action(settings, group, schema));
}
var results = await Task.WhenAll(tasks.ToArray());
foreach (var result in results)
{
// if any fail, all fail
if (result == false)
{
return false;
}
}
return true;
}
/// <summary>
/// Verify that the targeted CCD bucket exists or create it.
/// </summary>
/// <param name="input">Addressables data builder context</param>
/// <returns></returns>
public async Task<bool> VerifyTargetBucket(AddressablesDataBuilderInput input)
{
if (input.AddressableSettings == null)
{
string error;
if (EditorApplication.isUpdating)
error = "Addressable Asset Settings does not exist. EditorApplication.isUpdating was true.";
else if (EditorApplication.isCompiling)
error = "Addressable Asset Settings does not exist. EditorApplication.isCompiling was true.";
else
error = "Addressable Asset Settings does not exist. Failed to create.";
Debug.LogError(error);
return false;
}
if (!hasRemoteGroups(input.AddressableSettings))
{
Debug.LogWarning("No Addressable Asset Groups have been marked remote or the current profile is not using CCD.");
if (input.AddressableSettings.BuildRemoteCatalog)
{
Debug.LogWarning("A remote catalog will be built without any remote Asset Bundles.");
}
}
// Reclean directory before every build
if (Directory.Exists(AddressableAssetSettings.kCCDBuildDataPath))
{
Directory.Delete(AddressableAssetSettings.kCCDBuildDataPath, true);
}
return await LoopGroups(input.AddressableSettings, verifyTargetBucket);
}
internal bool hasRemoteGroups(AddressableAssetSettings settings)
{
foreach (var group in settings.groups)
{
if (group == null)
{
continue;
}
var schema = group.GetSchema<BundledAssetGroupSchema>();
if (schema == null)
{
continue;
}
var foundGroupType = GetGroupType(settings, schema);
if (isCCDGroup(foundGroupType))
{
return true;
}
}
return false;
}
internal bool isCCDGroup(ProfileGroupType groupType)
{
if (groupType == null)
{
return false;
}
if (IsUsingManager(groupType))
{
return true;
}
return groupType.GroupTypePrefix.StartsWith("CCD");
}
internal async Task<bool> verifyTargetBucket(AddressableAssetSettings settings, AddressableAssetGroup group, BundledAssetGroupSchema schema)
{
AddressableAssetSettings.NullifyBundleFileIds(group);
// if not using the manager try to lookup the bucket and verify it's not promotion only
var existingGroupType = GetDefaultGroupType(ProfileDataSourceSettings.GetSettings());
if (!IsUsingManager(settings, schema))
{
if (existingGroupType != null)
{
var promotionOnly = IsPromotionOnlyBucket(settings, schema);
if (promotionOnly)
{
Debug.LogError("Cannot upload to Promotion Only bucket.");
return false;
}
}
return true;
}
// CcdManagedData.ConfigState.Override means it has been overriden by the customer at build time
if (settings.m_CcdManagedData.State == CcdManagedData.ConfigState.Override)
{
return true;
}
// Target bucket should only be verified if Automatic profile type
settings.m_CcdManagedData.EnvironmentId = ProfileDataSourceSettings.GetSettings().currentEnvironment.id;
settings.m_CcdManagedData.EnvironmentName = ProfileDataSourceSettings.GetSettings().currentEnvironment.name;
// existing automatic bucket loaded from cache
if (existingGroupType != null)
{
var promotionOnly = IsPromotionOnlyBucket(settings, schema);
if (promotionOnly)
{
Debug.LogError("Cannot upload to Promotion Only bucket.");
return false;
}
settings.m_CcdManagedData.BucketId = existingGroupType
.GetVariableBySuffix($"{nameof(CcdBucket)}{nameof(CcdBucket.Id)}").Value;
settings.m_CcdManagedData.Badge = existingGroupType
.GetVariableBySuffix($"{nameof(CcdBadge)}{nameof(CcdBadge.Name)}").Value;
}
// otherwise try to create
else
{
try
{
var api = CcdManagement.Instance;
var createdBucket = await CreateManagedBucket(api, ProfileDataSourceSettings.GetSettings().currentEnvironment.id,
EditorUserBuildSettings.activeBuildTarget.ToString());
if (createdBucket.Attributes.PromoteOnly)
{
Debug.LogError("Cannot upload to Promotion Only bucket.");
return false;
}
settings.m_CcdManagedData.BucketId = createdBucket.Id.ToString();
settings.m_CcdManagedData.Badge = "latest";
}
catch (Exception e)
{
Debug.LogError(e.ToString());
return false;
}
}
return true;
}
/// <summary>
/// Download addressables_content_state.bin from the CCD managed bucket.
/// </summary>
/// <param name="input">Addressables data builder context</param>
/// <returns></returns>
public async Task<bool> DownloadContentStateBin(AddressablesDataBuilderInput input)
{
if (!input.AddressableSettings.BuildRemoteCatalog)
{
Debug.LogWarning("Not downloading content state because 'Build Remote Catalog' is not checked in Addressable Asset Settings. This will disable content updates");
return true;
}
var settings = input.AddressableSettings;
var groupType = getRemoteCatalogGroupType(settings);
if (groupType == null)
{
Debug.LogError("Could not find remote catalog paths");
return false;
}
if (!this.isCCDGroup(groupType))
{
Debug.LogError("Content state could not be downloaded as the remote catalog is not targeting CCD");
return false;
}
try
{
SetEnvironmentId(settings, groupType);
var bucketId = GetBucketId(settings, groupType);
if (bucketId == null)
{
Debug.LogError("Content state could not be downloaded as no bucket was specified. This is populated for managed profiles in the VerifyBucket event.");
return false;
}
var api = CcdManagement.Instance;
var ccdEntry = await GetEntryByPath(api, new Guid(bucketId), k_ContentStatePath);
if (ccdEntry != null)
{
var contentStream = await api.GetContentAsync(new EntryOptions(new Guid(bucketId), ccdEntry.Entryid));
var contentStatePath = Path.Combine(settings.GetContentStateBuildPath(), k_ContentStatePath);
if (!Directory.Exists(contentStatePath))
Directory.CreateDirectory(Path.GetDirectoryName(contentStatePath));
else if (File.Exists(contentStatePath))
File.Delete(contentStatePath);
using (var fileStream = File.Create(contentStatePath))
{
contentStream.CopyTo(fileStream);
}
}
}
catch (Exception e)
{
Debug.LogError(e.ToString());
return false;
}
return true;
}
/// <summary>
/// Upload content to the CCD managed bucket and create a release.
/// </summary>
/// <param name="input">Addressables data builder context</param>
/// <param name="result">Addressables build result</param>
/// <returns></returns>
public async Task<bool> UploadAndRelease(AddressablesDataBuilderInput input,
AddressablesPlayerBuildResult result)
{
// Verify files exist that need uploading
var foundRemoteContent = result.FileRegistry?.GetFilePaths()
.Any(path => path.StartsWith(AddressableAssetSettings.kCCDBuildDataPath)) == true;
if (!foundRemoteContent)
{
Debug.LogWarning(
"Skipping upload and release as no remote content was found to upload. Ensure you have at least one content group's 'Build & Load Path' set to Remote.");
return false;
}
try
{
//Getting files
Debug.Log("Creating and uploading entries");
var startDirectory = new DirectoryInfo(AddressableAssetSettings.kCCDBuildDataPath);
var buildData = CreateData(startDirectory);
//Creating a release for each bucket
var defaultEnvironmentId = ProfileDataSourceSettings.GetSettings().currentEnvironment.id;
await UploadAndRelease(CcdManagement.Instance, input.AddressableSettings, defaultEnvironmentId, buildData);
}
catch (Exception e)
{
Debug.LogError(e.ToString());
return false;
}
return true;
}
private ProfileGroupType getRemoteCatalogGroupType(AddressableAssetSettings settings)
{
var buildPath = settings.RemoteCatalogBuildPath;
var loadPath = settings.RemoteCatalogLoadPath;
var groupType = GetGroupType(settings, buildPath, loadPath);
return groupType;
}
/// <summary>
/// Upload addressables_content_state.bin to the CCD managed bucket.
/// </summary>
/// <param name="input">Addressables data builder context</param>
/// <param name="result">Addressables build result</param>
/// <returns></returns>
public async Task<bool> UploadContentState(AddressablesDataBuilderInput input,
AddressablesPlayerBuildResult result)
{
if (!input.AddressableSettings.BuildRemoteCatalog)
{
Debug.LogWarning("Not uploading content state, because 'Build Remote Catalog' is not checked in Addressable Asset Settings. This will disable content updates");
return true;
}
var settings = input.AddressableSettings;
var groupType = getRemoteCatalogGroupType(settings);
if (groupType == null)
{
Debug.LogError("Could not find remote catalog paths");
return false;
}
if (!this.isCCDGroup(groupType))
{
Debug.LogError("Content state could not be uploaded as the remote catalog is not targeting CCD");
return false;
}
try
{
SetEnvironmentId(settings, groupType);
var bucketId = GetBucketId(settings, groupType);
if (bucketId == null)
{
Debug.LogError("Content state could not be uploaded as no bucket was specified. This is populated for managed profiles in the VerifyBucket event.");
return false;
}
var api = CcdManagement.Instance;
var contentStatePath = Path.Combine(settings.GetContentStateBuildPath(), k_ContentStatePath);
if (!File.Exists(contentStatePath))
{
Debug.LogError($"Content state file is missing {contentStatePath}");
return false;
}
var contentHash = AddressableAssetUtility.GetMd5Hash(contentStatePath);
using (var stream = File.OpenRead(contentStatePath))
{
var entryModelOptions = new EntryModelOptions(k_ContentStatePath, contentHash, (int)stream.Length)
{
UpdateIfExists = true
};
var createdEntry = await api.CreateOrUpdateEntryByPathAsync(
new EntryByPathOptions(new Guid(bucketId), k_ContentStatePath),
entryModelOptions);
var uploadContentOptions = new UploadContentOptions(
new Guid(bucketId), createdEntry.Entryid, stream);
await api.UploadContentAsync(uploadContentOptions);
}
}
catch (Exception e)
{
Debug.LogError(e.ToString());
return false;
}
return true;
}
internal bool IsPromotionOnlyBucket(AddressableAssetSettings settings, BundledAssetGroupSchema schema)
{
if (schema != null)
{
var foundGroupType = GetGroupType(settings, schema);
if (foundGroupType != null && foundGroupType.GroupTypePrefix.StartsWith("CCD"))
{
if (bool.Parse(foundGroupType.GetVariableBySuffix(nameof(CcdBucket.Attributes.PromoteOnly)).Value))
{
Debug.LogError("Cannot upload to Promotion Only bucket.");
return true;
}
}
}
return false;
}
internal ProfileGroupType GetGroupType(AddressableAssetSettings settings, BundledAssetGroupSchema schema)
{
return GetGroupType(settings, schema.BuildPath, schema.LoadPath);
}
internal ProfileGroupType GetGroupType(AddressableAssetSettings settings, ProfileValueReference buildPath, ProfileValueReference loadPath)
{
// we need the "unresolved" value since we're tring to match it to its original type
Debug.Log("Loading from active profile id " + settings.activeProfileId);
var buildPathValue = settings.profileSettings.GetValueById(settings.activeProfileId, buildPath.Id);
var loadPathValue = settings.profileSettings.GetValueById(settings.activeProfileId, loadPath.Id);
Debug.Log(loadPathValue);
if (buildPathValue == null || loadPathValue == null)
{
return null;
}
var groupType = new ProfileGroupType("temp");
groupType.AddVariable(new ProfileGroupType.GroupTypeVariable(AddressableAssetSettings.kBuildPath, buildPathValue));
groupType.AddVariable(new ProfileGroupType.GroupTypeVariable(AddressableAssetSettings.kLoadPath, loadPathValue));
return ProfileDataSourceSettings.GetSettings().FindGroupType(groupType);
}
internal bool IsUsingManager(AddressableAssetSettings settings, BundledAssetGroupSchema schema)
{
var groupType = GetGroupType(settings, schema);
return IsUsingManager(groupType);
}
internal bool IsUsingManager(ProfileGroupType groupType)
{
if (groupType == null)
{
return false;
}
return groupType.GroupTypePrefix == AddressableAssetSettings.CcdManagerGroupTypePrefix;
}
internal void SetEnvironmentId(AddressableAssetSettings settings, ProfileGroupType groupType)
{
string environmentId = null;
if (!IsUsingManager(groupType))
{
// if not using the manager load the bucketID from the group type
environmentId = groupType.GetVariableBySuffix($"{nameof(ProfileDataSourceSettings.Environment)}{nameof(ProfileDataSourceSettings.Environment.id)}").Value;
}
else if (settings.m_CcdManagedData != null)
{
environmentId = settings.m_CcdManagedData.EnvironmentId;
}
if (environmentId == null)
{
throw new Exception("unable to determine environment ID.");
}
CcdManagement.SetEnvironmentId(environmentId);
}
internal string GetBucketId(AddressableAssetSettings settings, ProfileGroupType groupType)
{
if (!IsUsingManager(groupType))
{
// if not using the manager load the bucketID from the group type
return groupType.GetVariableBySuffix($"{nameof(CcdBucket)}{nameof(CcdBucket.Id)}").Value;
}
if (settings.m_CcdManagedData != null)
{
return settings.m_CcdManagedData.BucketId;
}
return null;
}
static ProfileGroupType GetDefaultGroupType(ProfileDataSourceSettings dataSourceSettings)
{
//Find existing bucketId
var groupTypes = dataSourceSettings.GetGroupTypesByPrefix(string.Join(
ProfileGroupType.k_PrefixSeparator.ToString(), "CCD", dataSourceSettings.currentEnvironment.projectGenesisId,
dataSourceSettings.currentEnvironment.id));
return groupTypes.FirstOrDefault(gt =>
gt.GetVariableBySuffix($"{nameof(CcdBucket)}{nameof(CcdBucket.Name)}").Value ==
EditorUserBuildSettings.activeBuildTarget.ToString());
}
async Task<CcdBucket> CreateManagedBucket(ICcdManagementServiceSdk api, string envId, string bucketName)
{
CcdBucket ccdBucket;
try
{
CcdManagement.SetEnvironmentId(envId);
ccdBucket = await api.CreateBucketAsync(
new CreateBucketOptions(bucketName));
}
catch (CcdManagementException e)
{
if (e.ErrorCode == CcdManagementErrorCodes.AlreadyExists)
{
var buckets = await ProfileDataSourceSettings.GetAllBucketsAsync(envId);
ccdBucket = buckets.First(bucket =>
bucket.Value.Name == EditorUserBuildSettings.activeBuildTarget.ToString()).Value;
}
else
{
throw;
}
}
return ccdBucket;
}
async Task<CcdEntry> GetEntryByPath(ICcdManagementServiceSdk api, Guid bucketId, string path)
{
CcdEntry ccdEntry = null;
try
{
ccdEntry = await api.GetEntryByPathAsync(new EntryByPathOptions(bucketId, path));
}
catch (CcdManagementException e)
{
if (e.ErrorCode != CommonErrorCodes.NotFound)
{
throw;
}
}
return ccdEntry;
}
CcdBuildDataFolder CreateData(DirectoryInfo startDirectory)
{
var buildDataFolder = new CcdBuildDataFolder
{
Name = AddressableAssetSettings.kCCDBuildDataPath,
Location = startDirectory.FullName
};
buildDataFolder.GetChildren(startDirectory);
return buildDataFolder;
}
int StartProgress(string description)
{
#if UNITY_2020_1_OR_NEWER
return Progress.Start("CCD", description, Progress.Options.Managed);
#else
Debug.Log(description);
return -1;
#endif
}
void RemoveProgress(int progressId)
{
#if UNITY_2020_1_OR_NEWER
Progress.Remove(progressId);
#endif
}
void ReportProgress(int progressId, float progress, string message)
{
#if UNITY_2020_1_OR_NEWER
Progress.Report(progressId, progress, message);
#else
Debug.Log($"[{progress}] {message}");
#endif
}
// Do not use Addressable.Log in this method as it may not be executed on the main thread
async Task UploadAndRelease(ICcdManagementServiceSdk api, AddressableAssetSettings settings, string defaultEnvironmentId, CcdBuildDataFolder buildData)
{
var progressId = StartProgress("Upload and Release");
try
{
foreach (var env in buildData.Environments)
{
CcdManagement.SetEnvironmentId(env.Name);
if (env.Name == ProfileDataSourceSettings.MANAGED_ENVIRONMENT)
{
CcdManagement.SetEnvironmentId(defaultEnvironmentId);
}
foreach (var bucket in env.Buckets)
{
Guid bucketId;
var bucketIdString = bucket.Name == ProfileDataSourceSettings.MANAGED_BUCKET
? settings.m_CcdManagedData.BucketId
: bucket.Name;
if (String.IsNullOrEmpty(bucketIdString))
{
Debug.LogError($"Invalid bucket ID for {bucket.Name}");
continue;
}
bucketId = Guid.Parse(bucketIdString);
foreach (var badge in bucket.Badges)
{
if (badge.Name == ProfileDataSourceSettings.MANAGED_BADGE)
{
badge.Name = "latest";
}
var entries = new List<CcdReleaseEntryCreate>();
var total = badge.Files.Count();
for (var i = 0; i < total; i++)
{
var file = badge.Files[i];
var contentHash = AddressableAssetUtility.GetMd5Hash(file.FullName);
using (var stream = File.OpenRead(file.FullName))
{
var entryPath = file.Name;
var entryModelOptions = new EntryModelOptions(entryPath, contentHash, (int)stream.Length)
{
UpdateIfExists = true
};
ReportProgress(progressId, (i + 1) / total, $"Creating Entry {entryPath}");
var createdEntry = await api.CreateOrUpdateEntryByPathAsync(new EntryByPathOptions(bucketId, entryPath),
entryModelOptions).ConfigureAwait(false);
Debug.Log($"Created Entry {entryPath}");
ReportProgress(progressId, (i + 1) / total, $"Uploading Entry {entryPath}");
var uploadContentOptions = new UploadContentOptions(bucketId, createdEntry.Entryid, stream);
await api.UploadContentAsync(uploadContentOptions).ConfigureAwait(false);
ReportProgress(progressId, (i + 1) / total, $"Uploaded Entry {entryPath}");
entries.Add(new CcdReleaseEntryCreate(createdEntry.Entryid, createdEntry.CurrentVersionid));
}
}
// Add content_sate.bin to release if present
var contentStateEntry = await GetEntryByPath(api, bucketId, k_ContentStatePath);
if (contentStateEntry != null)
entries.Add(new CcdReleaseEntryCreate(contentStateEntry.Entryid, contentStateEntry.CurrentVersionid));
//Creating release
ReportProgress(progressId, total, "Creating release");
Debug.Log("Creating release.");
var release = await api.CreateReleaseAsync(new CreateReleaseOptions(bucketId)
{
Entries = entries,
Notes = $"Automated release created for {badge.Name}"
}).ConfigureAwait(false);
Debug.Log($"Release {release.Releaseid} created.");
//Don't update latest badge (as it always updates)
if (badge.Name != "latest")
{
ReportProgress(progressId, total, "Updating badge");
Debug.Log("Updating badge.");
var badgeRes = await api.AssignBadgeAsync(new AssignBadgeOptions(bucketId, badge.Name, release.Releaseid))
.ConfigureAwait(false);
Debug.Log($"Badge {badgeRes.Name} updated.");
}
}
}
}
}
catch (Exception e)
{
Debug.LogError(e.ToString());
}
finally
{
RemoveProgress(progressId);
}
}
}
}
#endif