using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
#if (ENABLE_CCD && UNITY_2019_4_OR_NEWER)
using Unity.Services.CCD.Management;
using Unity.Services.CCD.Management.Apis.Badges;
using Unity.Services.CCD.Management.Apis.Buckets;
using Unity.Services.CCD.Management.Badges;
using Unity.Services.CCD.Management.Buckets;
using Unity.Services.CCD.Management.Http;
using Unity.Services.CCD.Management.Models;
#endif
namespace UnityEditor.AddressableAssets.Settings
{
///
/// Scriptable Object that holds data source setting information for the profile data source dropdown window
///
public class ProfileDataSourceSettings : ScriptableObject, ISerializationCallbackReceiver
{
const string DEFAULT_PATH = "Assets/AddressableAssetsData";
const string DEFAULT_NAME = "ProfileDataSourceSettings";
const string CONTENT_RANGE_HEADER = "Content-Range";
static string DEFAULT_SETTING_PATH = $"{DEFAULT_PATH}/{DEFAULT_NAME}.asset";
///
/// Group types that exist within the settings object
///
[SerializeField]
public List profileGroupTypes = new List();
///
/// Creates, if needed, and returns the profile data source settings for the project
///
/// Desired path to put settings
/// Desired name for settings
///
public static ProfileDataSourceSettings Create(string path = null, string settingName = null)
{
ProfileDataSourceSettings aa;
var assetPath = DEFAULT_SETTING_PATH;
if (path != null && settingName != null)
{
assetPath = $"{path}/{settingName}.asset";
}
aa = AssetDatabase.LoadAssetAtPath(assetPath);
if (aa == null)
{
Directory.CreateDirectory(path != null ? path : DEFAULT_PATH);
aa = CreateInstance();
AssetDatabase.CreateAsset(aa, assetPath);
aa = AssetDatabase.LoadAssetAtPath(assetPath);
aa.profileGroupTypes = CreateDefaultGroupTypes();
EditorUtility.SetDirty(aa);
}
return aa;
}
///
/// Gets the profile data source settings for the project
///
///
///
///
public static ProfileDataSourceSettings GetSettings(string path = null, string settingName = null)
{
ProfileDataSourceSettings aa;
var assetPath = DEFAULT_SETTING_PATH;
if (path != null && settingName != null)
{
assetPath = $"{path}/{settingName}.asset";
}
aa = AssetDatabase.LoadAssetAtPath(assetPath);
if (aa == null)
return Create();
return aa;
}
///
/// Creates a list of default group types that are automatically added on ProfileDataSourceSettings object creation
///
/// List of ProfileGroupTypes: Built-In and Editor Hosted
public static List CreateDefaultGroupTypes() => new List{CreateBuiltInGroupType(), CreateEditorHostedGroupType()};
static ProfileGroupType CreateBuiltInGroupType()
{
ProfileGroupType defaultBuiltIn = new ProfileGroupType(AddressableAssetSettings.LocalGroupTypePrefix);
defaultBuiltIn.AddVariable(new ProfileGroupType.GroupTypeVariable(AddressableAssetSettings.kBuildPath, AddressableAssetSettings.kLocalBuildPathValue));
defaultBuiltIn.AddVariable(new ProfileGroupType.GroupTypeVariable(AddressableAssetSettings.kLoadPath, AddressableAssetSettings.kLocalLoadPathValue));
return defaultBuiltIn;
}
static ProfileGroupType CreateEditorHostedGroupType()
{
ProfileGroupType defaultRemote = new ProfileGroupType(AddressableAssetSettings.EditorHostedGroupTypePrefix);
defaultRemote.AddVariable(new ProfileGroupType.GroupTypeVariable(AddressableAssetSettings.kBuildPath, AddressableAssetSettings.kRemoteBuildPathValue));
defaultRemote.AddVariable(new ProfileGroupType.GroupTypeVariable(AddressableAssetSettings.kLoadPath, AddressableAssetSettings.RemoteLoadPathValue));
return defaultRemote;
}
///
/// Given a valid profileGroupType, searches the settings and returns, if exists, the profile group type
///
///
/// ProfileGroupType if found, null otherwise
public ProfileGroupType FindGroupType(ProfileGroupType groupType)
{
ProfileGroupType result = null;
if (!groupType.IsValidGroupType())
{
throw new ArgumentException("Group Type is not valid. Group Type must include a build path and load path variables");
}
foreach (ProfileGroupType settingsGroupType in profileGroupTypes)
{
var buildPath = groupType.GetVariableBySuffix(AddressableAssetSettings.kBuildPath);
var foundBuildPath = settingsGroupType.ContainsVariable(buildPath);
var loadPath = groupType.GetVariableBySuffix(AddressableAssetSettings.kLoadPath);
var foundLoadPath = settingsGroupType.ContainsVariable(loadPath);
if (foundBuildPath && foundLoadPath)
{
result = settingsGroupType;
break;
}
}
return result;
}
///
/// Retrieves a list of ProfileGroupType that matches the given prefix
///
/// prefix to search by
/// List of ProfileGroupType
public List GetGroupTypesByPrefix(string prefix)
{
return profileGroupTypes.Where((groupType) => groupType.GroupTypePrefix.StartsWith(prefix)).ToList();
}
#if (ENABLE_CCD && UNITY_2019_4_OR_NEWER)
///
/// Updates the CCD buckets and badges with the data source settings
///
/// Project Id connected to Unity Services
/// Whether or not to show debug logs or not
/// List of ProfileGroupType
public static async Task> UpdateCCDDataSourcesAsync(string projectId, bool showInfoLog)
{
if (showInfoLog) Addressables.Log("Syncing CCD Buckets and Badges.");
var settings = GetSettings();
var profileGroupTypes = new List();
profileGroupTypes.AddRange(CreateDefaultGroupTypes());
await CCDManagementAPIService.SetConfigurationAuthHeader(CloudProjectSettings.accessToken);
var bucketDictionary = await GetAllBucketsAsync(projectId);
foreach (var kvp in bucketDictionary)
{
var bucket = kvp.Value;
var badges = await GetAllBadgesAsync(projectId, bucket.Id.ToString());
if (badges.Count == 0) badges.Add(new CcdBadge(name: "latest"));
foreach (var badge in badges)
{
var groupType = new ProfileGroupType($"CCD{ProfileGroupType.k_PrefixSeparator}{projectId}{ProfileGroupType.k_PrefixSeparator}{bucket.Id}{ProfileGroupType.k_PrefixSeparator}{badge.Name}");
groupType.AddVariable(new ProfileGroupType.GroupTypeVariable($"{nameof(CcdBucket)}{nameof(CcdBucket.Name)}", bucket.Name));
groupType.AddVariable(new ProfileGroupType.GroupTypeVariable($"{nameof(CcdBucket)}{nameof(CcdBucket.Id)}", bucket.Id.ToString()));
groupType.AddVariable(new ProfileGroupType.GroupTypeVariable($"{nameof(CcdBadge)}{nameof(CcdBadge.Name)}", badge.Name));
groupType.AddVariable(new ProfileGroupType.GroupTypeVariable(nameof(CcdBucket.Attributes.PromoteOnly), bucket.Attributes.PromoteOnly.ToString()));
string buildPath = $"{AddressableAssetSettings.kCCDBuildDataPath}/{bucket.Id}/{badge.Name}";
groupType.AddVariable(new ProfileGroupType.GroupTypeVariable(AddressableAssetSettings.kBuildPath, buildPath));
string loadPath = $"https://{projectId}.client-api.unity3dusercontent.com/client_api/v1/buckets/{bucket.Id}/release_by_badge/{badge.Name}/entry_by_path/content/?path=";
groupType.AddVariable(new ProfileGroupType.GroupTypeVariable(AddressableAssetSettings.kLoadPath, loadPath));
profileGroupTypes.Add(groupType);
}
}
settings.profileGroupTypes = profileGroupTypes;
if (showInfoLog) Addressables.Log("Successfully synced CCD Buckets and Badges.");
EditorUtility.SetDirty(settings);
AddressableAssetUtility.OpenAssetIfUsingVCIntegration(settings);
return settings.profileGroupTypes;
}
private static async Task> GetAllBucketsAsync(string projectId)
{
int numBuckets = Int32.MaxValue;
int page = 1;
List buckets = new List();
BucketsApiClient client = new BucketsApiClient(new HttpClient());
do
{
ListBucketsByProjectRequest request = new ListBucketsByProjectRequest(projectId, page);
Response> response = await client.ListBucketsByProjectAsync(request);
if (response.Result.Count > 0)
buckets.AddRange(response.Result);
if (page == 1)
{
response.Headers.TryGetValue(CONTENT_RANGE_HEADER, out string contentLength);
// content-range: items x-y/z => grab z
numBuckets = Int32.Parse(contentLength.Split('/')[1]);
}
page++;
} while (buckets.Count < numBuckets);
return buckets.ToDictionary(kvp => kvp.Id, kvp => kvp);
}
private static async Task> GetAllBadgesAsync(string projectId, string bucketId)
{
int numBadges = Int32.MaxValue;
int page = 1;
List badges = new List();
BadgesApiClient client = new BadgesApiClient(new HttpClient());
do
{
ListBadgesRequest request = new ListBadgesRequest(bucketId, projectId, page);
Response> response = await client.ListBadgesAsync(request);
if (response.Result.Count > 0)
badges.AddRange(response.Result);
if (page == 1)
{
response.Headers.TryGetValue(CONTENT_RANGE_HEADER, out string contentLength);
// content-range: items x-y/z => grab z
numBadges = Int32.Parse(contentLength.Split('/')[1]);
}
page++;
} while (badges.Count < numBadges);
return badges;
}
#endif
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
}
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
// Ensure static Group types have the correct string
// Local
var types = GetGroupTypesByPrefix(AddressableAssetSettings.LocalGroupTypePrefix);
if (types == null || types.Count == 0)
profileGroupTypes.Add(CreateBuiltInGroupType());
else
{
types[0].AddOrUpdateVariable(new ProfileGroupType.GroupTypeVariable(AddressableAssetSettings.kBuildPath,
AddressableAssetSettings.kLocalBuildPathValue));
types[0].AddOrUpdateVariable(new ProfileGroupType.GroupTypeVariable(AddressableAssetSettings.kLoadPath,
AddressableAssetSettings.kLocalLoadPathValue));
}
// Editor Hosted
types = GetGroupTypesByPrefix(AddressableAssetSettings.EditorHostedGroupTypePrefix);
if (types.Count == 0)
profileGroupTypes.Add(CreateEditorHostedGroupType());
else
{
types[0].AddOrUpdateVariable(new ProfileGroupType.GroupTypeVariable(AddressableAssetSettings.kBuildPath,
AddressableAssetSettings.kRemoteBuildPathValue));
types[0].AddOrUpdateVariable(new ProfileGroupType.GroupTypeVariable(AddressableAssetSettings.kLoadPath,
AddressableAssetSettings.RemoteLoadPathValue));
}
}
}
}