// 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; } } } }