Use case: Update an asset's files
You can use the Unity Cloud Assets package to edit file metadata and download file content.
The SDK supports different workflows for users with different roles.
Organization or Asset Manager Project role | Download files | Update files |
---|---|---|
Asset Management Viewer |
no | no |
Asset Management Consumer |
yes | no |
Asset Management Contributor |
yes | yes |
Organization Owner |
yes | yes |
Before you start
Before you start, you must:
Set up a Unity scene in the Unity Editor with an Organization and Project browser. See Get started with Assets for more information.
Have some assets in the cloud. There are several ways to do so:
- You can create assets through the Get started with Assets.
- You can create assets through the dashboard; see the Managing assets on the dashboard documentation.
You should also have uploaded files to an asset; see the Create files use case.
How do I...?
List the files of an asset
By default, when you get an asset, the files associated with are not included in the response. To get files associated to an asset:
- Open the
AssetManagementBehaviour
script you created. - Add the following code to the end of the class:
CancellationTokenSource m_DatasetCancellationSource;
CancellationTokenSource m_FileCancellationSource;
public List<IDataset> Datasets { get; } = new();
public DatasetId? CurrentDatasetId { get; private set; }
Dictionary<DatasetId, string> DatasetNames { get; } = new();
public Dictionary<string, FileProperties> FileProperties { get; } = new();
public string CurrentFilePath { get; set; }
public async Task GetDatasetsAsync()
{
var datasetId = CurrentDatasetId;
CurrentDatasetId = null;
CleanFileCancellation();
FileProperties.Clear();
CleanDatasetCancellation();
Datasets.Clear();
DatasetNames.Clear();
if (CurrentAsset == null) return;
m_DatasetCancellationSource = new CancellationTokenSource();
var token = m_DatasetCancellationSource.Token;
var datasetList = CurrentAsset.ListDatasetsAsync(Range.All, token);
await foreach (var dataset in datasetList)
{
Datasets.Add(dataset);
if (datasetId == dataset.Descriptor.DatasetId)
{
CurrentDatasetId = dataset.Descriptor.DatasetId;
}
var properties = await dataset.GetPropertiesAsync(token);
if (token.IsCancellationRequested) break;
DatasetNames[dataset.Descriptor.DatasetId] = properties.Name;
}
}
public string GetDatasetName(DatasetId datasetId)
{
return DatasetNames.TryGetValue(datasetId, out var datasetName) ? datasetName : datasetId.ToString();
}
public async Task SetSelectedDatasetAsync(DatasetId? datasetId)
{
CurrentDatasetId = datasetId;
await GetFilesAsync();
}
public async Task GetFilesAsync()
{
var filePath = CurrentFilePath;
CurrentFilePath = null;
CleanFileCancellation();
FileProperties.Clear();
if (CurrentDatasetId == null) return;
var dataset = Datasets.FirstOrDefault(d => d.Descriptor.DatasetId == CurrentDatasetId);
if (dataset == null) return;
m_FileCancellationSource = new CancellationTokenSource();
var token = m_FileCancellationSource.Token;
var fileList = dataset.ListFilesAsync(Range.All, token);
await foreach (var file in fileList)
{
if (filePath == file.Descriptor.Path)
{
CurrentFilePath = file.Descriptor.Path;
}
var properties = await file.GetPropertiesAsync(token);
FileProperties[file.Descriptor.Path] = properties;
}
}
void CleanDatasetCancellation()
{
if (m_DatasetCancellationSource != null)
{
m_DatasetCancellationSource.Cancel();
m_DatasetCancellationSource.Dispose();
}
m_DatasetCancellationSource = null;
}
void CleanFileCancellation()
{
if (m_FileCancellationSource != null)
{
m_FileCancellationSource.Cancel();
m_FileCancellationSource.Dispose();
}
m_FileCancellationSource = null;
}
async Task<IFile> GetFileAsync(string filePath)
{
if (CurrentDatasetId == null || string.IsNullOrEmpty(filePath)) return null;
var dataset = Datasets.FirstOrDefault(d => d.Descriptor.DatasetId == CurrentDatasetId)
?? await CurrentAsset.GetDatasetAsync(CurrentDatasetId.Value, CancellationToken.None);
return await dataset.GetFileAsync(CurrentFilePath, CancellationToken.None);
}
The code snippet populates the Files
property of the selected asset.
Download a file
To download the file of an asset, follow these steps:
- Open the
AssetManagementBehaviour
script you created. - Add the following code to the end of the class:
class LogProgress : IProgress<HttpProgress>
{
public void Report(HttpProgress value)
{
if (!value.DownloadProgress.HasValue) return;
Debug.Log($"Download progress: {value.DownloadProgress * 100} %");
}
}
public async Task DownloadFileAsync(string filePath)
{
const string dialogHeader = "Download file to location:";
var defaultFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var folder = UnityEditor.EditorUtility.OpenFolderPanel(dialogHeader, defaultFolder, "");
if (string.IsNullOrEmpty(folder)) return;
var downloadPath = Path.Combine(folder, filePath);
try
{
// Create the necessary directories
var directory = Path.GetDirectoryName(downloadPath);
if (!string.IsNullOrEmpty(directory))
{
Directory.CreateDirectory(directory);
}
var file = await GetFileAsync(filePath);
await using var destination = File.OpenWrite(downloadPath);
var progress = new LogProgress();
await file.DownloadAsync(destination, progress, default);
Debug.Log($"Asset file downloaded: {filePath}.");
}
catch (Exception e)
{
Debug.LogError($"Failed to download asset file: {filePath}. {e}");
if (File.Exists(downloadPath))
{
File.Delete(downloadPath);
}
}
}
The code snippet does the following:
- Gets the files of an asset.
- Downloads the selected file to the desktop.
- Prints a message to the console when the download is complete OR prints an error message if the download fails.
Update a file
The properties of the file that can be updated are the following:
- Description
- Tags
To update a file, follow these steps:
- Open the
AssetManagementBehaviour
script you created. - Add the following code to the end of the class:
public async Task UpdateFileAsync(IFileUpdate fileUpdate)
{
var file = await GetFileAsync(CurrentFilePath);
if (file == null) return;
try
{
await file.UpdateAsync(fileUpdate, CancellationToken.None);
await file.RefreshAsync(CancellationToken.None);
var properties = await file.GetPropertiesAsync(CancellationToken.None);
FileProperties[CurrentFilePath] = properties;
Debug.Log("File updated.");
}
catch (Exception e)
{
Debug.LogError($"Failed to update file. {e}");
throw;
}
}
The code snippet does the following:
- Updates the file with new data.
- Prints a message to the console on success.
Delete a file
Deleting a file involves removing all references to the file from the asset. For more information see the use case for Removing a file reference from a dataset.
Generating tags for a file
The service can generate a list of suggested tags for any image files in the following supported formats:
- JPEG
- PNG
- GIF
- TIFF
- WebP
The desired tags can then be added to the file through the update method.
To generate tags for a file, follow these steps:
- Open the
AssetManagementBehaviour
script you created. - Add the following code to the end of the class:
public IEnumerable<GeneratedTag> GeneratedTags { get; private set; }
CancellationTokenSource TagGenerationCancellationSource;
public async Task GenerateTagsAsync()
{
CancelTagGeneration();
TagGenerationCancellationSource = new CancellationTokenSource();
try
{
var file = await GetFileAsync(CurrentFilePath);
GeneratedTags = await file.GenerateSuggestedTagsAsync(TagGenerationCancellationSource.Token);
}
catch (OperationCanceledException)
{
Debug.Log($"Cancelled tag generation for {CurrentFilePath}.");
}
catch (Exception e)
{
Debug.LogError(e);
}
}
public void CancelTagGeneration()
{
if (TagGenerationCancellationSource != null)
{
TagGenerationCancellationSource.Cancel();
TagGenerationCancellationSource.Dispose();
}
TagGenerationCancellationSource = null;
GeneratedTags = null;
}
The code snippet does the following:
- Returns a list of generated tags for the file.
- Prints any errors to the console.
Add the UI for interacting with files
To create UI for interacting with files, follow these steps:
- In your Unity Project window, go to Assets > Scripts.
- Select and hold the
Assets/Scripts
folder. - Go to Create > C# Script. Name your script
UseCaseFileManagementExampleUI
. - Open the
UseCaseFileManagementExampleUI
script you created and replace the contents of the file with the following code sample:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Unity.Cloud.Assets;
using Unity.Cloud.Common;
using UnityEngine;
public class UseCaseFileManagementExampleUI : IAssetManagementUI
{
readonly AssetManagementBehaviour m_Behaviour;
public UseCaseFileManagementExampleUI(AssetManagementBehaviour behaviour)
{
m_Behaviour = behaviour;
}
public void OnGUI() { }
}
- In the same script, replace the
OnGUI
function with the following code:
IAsset m_CurrentAsset;
Vector2 m_DatasetsScrollPosition;
Vector2 m_FilesScrollPosition;
FileUpdate m_FileUpdate;
string m_TagsString = string.Empty;
public void OnGUI()
{
if (!m_Behaviour.IsProjectSelected) return;
if (m_Behaviour.CurrentAsset == null)
{
GUILayout.Label(" ! No asset selected !");
return;
}
if (m_CurrentAsset != m_Behaviour.CurrentAsset)
{
m_CurrentAsset = m_Behaviour.CurrentAsset;
m_FileUpdate = null;
m_Behaviour.CancelTagGeneration();
_ = m_Behaviour.GetDatasetsAsync();
}
GUILayout.BeginVertical();
DisplayDatasetSelection(m_Behaviour.Datasets.ToArray());
GUILayout.EndVertical();
if (m_Behaviour.CurrentDatasetId == null)
{
GUILayout.Label("! No dataset selected !");
return;
}
GUILayout.BeginVertical();
DisplayFileSelection(m_Behaviour.FileProperties.Keys.ToArray());
GUILayout.EndVertical();
if (m_Behaviour.CurrentFilePath == null)
{
GUILayout.Label("! No file selected !");
return;
}
GUILayout.BeginVertical();
DisplaySelectedFile();
GUILayout.EndVertical();
}
void DisplayDatasetSelection(IReadOnlyCollection<IDataset> datasets)
{
if (GUILayout.Button("Refresh", GUILayout.Width(60)))
{
_ = m_Behaviour.GetDatasetsAsync();
return;
}
GUILayout.Space(5);
if (datasets.Count == 0)
{
GUILayout.Label("No datasets.");
return;
}
m_DatasetsScrollPosition = GUILayout.BeginScrollView(m_DatasetsScrollPosition, GUILayout.ExpandHeight(true), GUILayout.Width(Screen.width * 0.15f));
foreach (var dataset in datasets)
{
GUILayout.BeginHorizontal();
GUILayout.Label(m_Behaviour.GetDatasetName(dataset.Descriptor.DatasetId));
GUI.enabled = dataset.Descriptor.DatasetId != m_Behaviour.CurrentDatasetId;
if (GUILayout.Button("Select", GUILayout.Width(60)))
{
m_FileUpdate = null;
m_Behaviour.CancelTagGeneration();
_ = m_Behaviour.SetSelectedDatasetAsync(dataset.Descriptor.DatasetId);
}
GUI.enabled = true;
GUILayout.EndHorizontal();
}
GUILayout.EndScrollView();
}
void DisplayFileSelection(IReadOnlyCollection<string> filePaths)
{
if (GUILayout.Button("Refresh", GUILayout.Width(60)))
{
m_FileUpdate = null;
m_Behaviour.CancelTagGeneration();
_ = m_Behaviour.GetFilesAsync();
return;
}
GUILayout.Space(5);
if (filePaths.Count == 0)
{
GUILayout.Label("No files.");
return;
}
m_FilesScrollPosition = GUILayout.BeginScrollView(m_FilesScrollPosition, GUILayout.MaxHeight(Screen.height * 0.8f), GUILayout.Width(Screen.width * 0.2f));
DisplayFiles(filePaths);
GUILayout.EndScrollView();
}
void DisplayFiles(IReadOnlyCollection<string> filePaths)
{
// Get a local copy of the list of asset files to avoid concurrent modification exceptions.
foreach (var filePath in filePaths)
{
if (!m_Behaviour.FileProperties.TryGetValue(filePath, out var fileProperties))
{
GUILayout.Label(filePath);
continue;
}
GUILayout.BeginHorizontal();
GUILayout.Label($"{filePath}");
GUI.enabled = filePath != m_Behaviour.CurrentFilePath;
if (GUILayout.Button("Select", GUILayout.Width(70)))
{
m_Behaviour.CurrentFilePath = filePath;
m_FileUpdate = new FileUpdate
{
Description = fileProperties.Description,
Tags = fileProperties.Tags?.ToArray() ?? Array.Empty<string>()
};
m_TagsString = string.Join(',', m_FileUpdate.Tags);
m_Behaviour.CancelTagGeneration();
}
GUI.enabled = true;
if (GUILayout.Button("Download", GUILayout.Width(70)))
{
_ = m_Behaviour.DownloadFileAsync(filePath);
}
GUILayout.EndHorizontal();
}
}
void DisplaySelectedFile()
{
if (!m_Behaviour.FileProperties.TryGetValue(m_Behaviour.CurrentFilePath, out var properties))
{
GUILayout.Label("! File properties not loaded !");
return;
}
GUILayout.BeginVertical(GUI.skin.box);
GUILayout.Label($"{m_Behaviour.CurrentFilePath}");
GUILayout.Label($"Status: {properties.StatusName}");
var createdDate = properties.AuthoringInfo?.Created.ToString("d") ?? "unknown";
GUILayout.Label($"Created on: {createdDate}");
var modifiedDate = properties.AuthoringInfo?.Updated.ToString("d") ?? "unknown";
GUILayout.Label($"Last modified on: {modifiedDate}");
GUILayout.Label($"Size: {properties.SizeBytes} bytes");
GUILayout.EndVertical();
GUILayout.Space(5);
GUILayout.Label("Description:");
m_FileUpdate.Description = GUILayout.TextField(m_FileUpdate.Description);
GUILayout.Label("Tags:");
m_TagsString = GUILayout.TextField(m_TagsString, GUILayout.ExpandWidth(true));
m_FileUpdate.Tags = m_TagsString.Split(',')
.Select(x => x.Trim())
.Where(x => !string.IsNullOrWhiteSpace(x))
.ToArray();
if (GUILayout.Button("Generate Tags"))
{
_ = m_Behaviour.GenerateTagsAsync();
}
DisplayGeneratedTags();
GUILayout.Space(5);
if (GUILayout.Button("Update"))
{
_ = m_Behaviour.UpdateFileAsync(m_FileUpdate);
}
}
void DisplayGeneratedTags()
{
if (m_Behaviour.GeneratedTags != null)
{
foreach (var tag in m_Behaviour.GeneratedTags)
{
GUILayout.BeginHorizontal();
GUI.enabled = !m_FileUpdate.Tags?.Contains(tag.Value) ?? true;
if (GUILayout.Button("Add", GUILayout.Width(40)))
{
if (string.IsNullOrWhiteSpace(m_TagsString))
{
m_TagsString = tag.Value;
}
else
{
m_TagsString += $", {tag.Value}";
}
}
GUILayout.Label($"{tag.Value}, Confidence: {tag.Confidence:F3}");
GUI.enabled = true;
GUILayout.EndHorizontal();
}
}
}
- Open the
AssetManagementUI
script you created and replace the contents of theAwake
function with the following code:
m_UI.Add(new OrganizationSelectionExampleUI(m_Behaviour));
m_UI.Add(new ProjectSelectionExampleUI(m_Behaviour));
m_UI.Add(new AssetSelectionExampleUI(m_Behaviour));
#if UNITY_EDITOR && !UNITY_WEBGL
m_UI.Add(new UseCaseFileManagementExampleUI(m_Behaviour));
#endif
The code snippet does the following:
- Displays a button to force refresh the list of files of the selected asset.
- Displays each file of the selected asset with a UI buttons to select and download.
- When a file is selected, displays a UI to generate tags and update the file's description and tags.