// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using EpicGames.Horde.Acls;
using EpicGames.Horde.Storage;
using EpicGames.Horde.Storage.Nodes;
#pragma warning disable CA1716 // Rename virtual/interface member ITool.Public so that it no longer conflicts with the reserved language keyword 'Public'.
namespace EpicGames.Horde.Tools
{
///
/// Describes a standalone, external tool hosted and deployed by Horde. Provides basic functionality for performing
/// gradual roll-out, versioning, etc...
///
public interface ITool
{
///
/// Identifier for the tool
///
ToolId Id { get; }
///
/// Name of the tool
///
string Name { get; }
///
/// Long-form description of the tool
///
string Description { get; }
///
/// Category for the tool on the dashboard
///
string? Category { get; }
///
/// Grouping key for merging tool versions together on the dashboard
///
string? Group { get; }
///
/// Supported platforms, as a NET runtime identifiers
///
IReadOnlyList? Platforms { get; }
///
/// Whether the tool is available to authenticated users
///
bool Public { get; }
///
/// Whether the tool is bundled with the server
///
bool Bundled { get; }
///
/// Whether to show the tool for download in UGS
///
bool ShowInUgs { get; }
///
/// Whether to show the tool for download in the dashboard
///
bool ShowInDashboard { get; }
///
/// Whether to show the tool for download in the toolbox
///
bool ShowInToolbox { get; }
///
/// User-defined metadata for this tool
///
IReadOnlyDictionary Metadata { get; }
///
/// Current deployments of this tool, sorted by time.
///
IReadOnlyList Deployments { get; }
///
/// Authorize a user to perform a particular action
///
/// Action the user is trying to perform
/// Identity of the user trying to perform the action
bool Authorize(AclAction action, ClaimsPrincipal principal);
///
/// Adds a new deployment to the given tool. The new deployment will replace the current active deployment.
///
/// Options for the new deployment
/// Stream containing the tool data
/// Cancellation token for the operation
/// Updated tool document, or null if it does not exist
Task CreateDeploymentAsync(ToolDeploymentConfig options, Stream stream, CancellationToken cancellationToken = default);
///
/// Adds a new deployment to the given tool. The new deployment will replace the current active deployment.
///
/// Options for the new deployment
/// Path to the root node containing the tool data
/// Cancellation token for the operation
/// Updated tool document, or null if it does not exist
Task CreateDeploymentAsync(ToolDeploymentConfig options, HashedBlobRefValue target, CancellationToken cancellationToken = default);
///
/// Gets the storage backend for this tool
///
IStorageBackend GetStorageBackend();
///
/// Gets the storage namespace for this particular tool
///
IStorageNamespace GetStorageNamespace();
}
///
/// Deployment of a tool
///
public interface IToolDeployment
{
///
/// Identifier for this deployment. A new identifier will be assigned to each created instance, so an identifier corresponds to a unique deployment.
///
ToolDeploymentId Id { get; }
///
/// Descriptive version string for this tool revision
///
string Version { get; }
///
/// Current state of this deployment
///
ToolDeploymentState State { get; }
///
/// Current progress of the deployment
///
double Progress { get; }
///
/// Last time at which the progress started. Set to null if the deployment was paused.
///
DateTime? StartedAt { get; }
///
/// Length of time over which to make the deployment
///
TimeSpan Duration { get; }
///
/// Namespace containing the tool
///
NamespaceId NamespaceId { get; }
///
/// Reference to this tool in Horde Storage.
///
RefName RefName { get; }
///
/// Handle to the tool data
///
IBlobRef Content { get; }
///
/// Updates the state of the current deployment
///
/// New state of the deployment
/// Cancellation token for the operation
///
Task UpdateAsync(ToolDeploymentState state, CancellationToken cancellationToken = default);
///
/// Opens a stream to the data for a particular deployment
///
/// Cancellation token for the operation
/// Stream for the data
Task OpenZipStreamAsync(CancellationToken cancellationToken = default);
}
///
/// Options for a new deployment
///
public class ToolDeploymentConfig
{
///
public string Version { get; set; } = "Unknown";
///
public TimeSpan Duration { get; set; }
///
/// Whether to create the deployment in a paused state
///
public bool CreatePaused { get; set; }
}
///
/// Extension methods for tools
///
public static class ToolExtensions
{
///
/// Gets the current deployment
///
/// Tool to query
/// Adoption phase for the caller. 0 which the
/// Current time
///
public static IToolDeployment? GetCurrentDeployment(this ITool tool, double phase, DateTime utcNow)
{
int idx = tool.Deployments.Count - 1;
for (; idx >= 0; idx--)
{
if (phase <= tool.Deployments[idx].GetProgressValue(utcNow) || idx == 0)
{
return tool.Deployments[idx];
}
}
return null;
}
///
/// Get the progress fraction for a particular deployment and time
///
///
///
///
public static double GetProgressValue(this IToolDeployment deployment, DateTime utcNow)
{
if (deployment.StartedAt == null)
{
return deployment.Progress;
}
else if (deployment.Duration > TimeSpan.Zero)
{
return Math.Clamp((utcNow - deployment.StartedAt.Value) / deployment.Duration, 0.0, 1.0);
}
else
{
return 1.0;
}
}
}
}