petrsvihlik/WopiHost

View on GitHub
src/WopiHost.Discovery/WopiDiscoverer.cs

Summary

Maintainability
A
3 hrs
Test Coverage
using System.Xml.Linq;
using WopiHost.Discovery.Enumerations;

namespace WopiHost.Discovery;

///<inheritdoc cref="IDiscoverer"/>
/// <summary>
/// Creates a new instance of the <see cref="WopiDiscoverer"/>, a class for examining the capabilities of the WOPI client.
/// </summary>
/// <param name="discoveryFileProvider">A service that provides the discovery file to examine.</param>
/// <param name="discoveryOptions"></param>
public class WopiDiscoverer(IDiscoveryFileProvider discoveryFileProvider, DiscoveryOptions discoveryOptions) : IDiscoverer
{
    private const string ElementNetZone = "net-zone";
    private const string ElementApp = "app";
    private const string ElementAction = "action";
    private const string AttrNetZoneName = "name";
    private const string AttrActionExtension = "ext";
    private const string AttrActionName = "name";
    private const string AttrActionUrl = "urlsrc";
    private const string AttrActionRequires = "requires";
    private const string AttrAppName = "name";
    private const string AttrAppFavicon = "favIconUrl";
    private const string AttrValCobalt = "cobalt";

    private AsyncExpiringLazy<IEnumerable<XElement>> _apps;

    private IDiscoveryFileProvider DiscoveryFileProvider { get; } = discoveryFileProvider;

    private DiscoveryOptions DiscoveryOptions { get; } = discoveryOptions;

    private AsyncExpiringLazy<IEnumerable<XElement>> Apps
    {
        get
        {
            return _apps ??= new AsyncExpiringLazy<IEnumerable<XElement>>(async metadata =>
            {
                return new TemporaryValue<IEnumerable<XElement>>
                {
                    Result = (await DiscoveryFileProvider.GetDiscoveryXmlAsync())
                    .Elements(ElementNetZone)
                    .Where(ValidateNetZone)
                    .Elements(ElementApp),

                    ValidUntil = DateTimeOffset.UtcNow.Add(DiscoveryOptions.RefreshInterval)
                };
            });
        }
    }

    internal async Task<IEnumerable<XElement>> GetAppsAsync() => await Apps.Value();

    private bool ValidateNetZone(XElement e)
    {
        var netZoneString = (string)e.Attribute(AttrNetZoneName);
        netZoneString = netZoneString.Replace("-", "", StringComparison.InvariantCulture);
        var success = Enum.TryParse(netZoneString, true, out NetZoneEnum netZone);
        return success && (netZone == DiscoveryOptions.NetZone);
    }

    ///<inheritdoc />
    public async Task<bool> SupportsExtensionAsync(string extension)
    {
        var query = (await GetAppsAsync()).Elements()
            .FirstOrDefault(e => (string)e.Attribute(AttrActionExtension) == extension);
        return query is not null;
    }

    ///<inheritdoc />
    public async Task<bool> SupportsActionAsync(string extension, WopiActionEnum action)
    {
        var actionString = action.ToString().ToUpperInvariant();

        var query = (await GetAppsAsync()).Elements().Where(e => (string)e.Attribute(AttrActionExtension) == extension && e.Attribute(AttrActionName).Value.Equals(actionString, StringComparison.InvariantCultureIgnoreCase));

        return query.Any();
    }

    ///<inheritdoc />
    public async Task<IEnumerable<string>> GetActionRequirementsAsync(string extension, WopiActionEnum action)
    {
        var actionString = action.ToString().ToUpperInvariant();

        var query = (await GetAppsAsync()).Elements().Where(e => (string)e.Attribute(AttrActionExtension) == extension && e.Attribute(AttrActionName).Value.Equals(actionString, StringComparison.InvariantCultureIgnoreCase)).Select(e => e.Attribute(AttrActionRequires).Value.Split(','));

        return query.FirstOrDefault();
    }

    ///<inheritdoc />
    public async Task<bool> RequiresCobaltAsync(string extension, WopiActionEnum action)
    {
        var requirements = await GetActionRequirementsAsync(extension, action);
        return requirements is not null && requirements.Contains(AttrValCobalt);
    }

    ///<inheritdoc />
    public async Task<string> GetUrlTemplateAsync(string extension, WopiActionEnum action)
    {
        var actionString = action.ToString().ToUpperInvariant();
        var query = (await GetAppsAsync()).Elements().Where(e => (string)e.Attribute(AttrActionExtension) == extension && e.Attribute(AttrActionName).Value.Equals(actionString, StringComparison.InvariantCultureIgnoreCase)).Select(e => e.Attribute(AttrActionUrl).Value);
        return query.FirstOrDefault();
    }

    ///<inheritdoc />
    public async Task<string> GetApplicationNameAsync(string extension)
    {
        var query = (await GetAppsAsync()).Where(e => e.Descendants(ElementAction).Any(d => (string)d.Attribute(AttrActionExtension) == extension)).Select(e => e.Attribute(AttrAppName).Value);

        return query.FirstOrDefault();
    }

    ///<inheritdoc />
    public async Task<Uri> GetApplicationFavIconAsync(string extension)
    {
        var query = (await GetAppsAsync()).Where(e => e.Descendants(ElementAction).Any(d => (string)d.Attribute(AttrActionExtension) == extension)).Select(e => e.Attribute(AttrAppFavicon).Value);
        var result = query.FirstOrDefault();
        return result is not null ? new Uri(result) : null;
    }
}