File: Helpers.cs
Web Access
Project: Microsoft.Arcade.Common.csproj (Microsoft.Arcade.Common)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Arcade.Common;

public class Helpers : IHelpers
{
    private static readonly char[] s_slashes = [ '/', '\\' ];

    public string RemoveTrailingSlash(string directoryPath)
    {
        return directoryPath.TrimEnd(s_slashes);
    }

    public string ComputeSha256Hash(string normalizedPath)
    {
        using (var hasher = SHA256.Create())
        {
            var dirHash = Convert.ToBase64String(hasher.ComputeHash(Encoding.UTF8.GetBytes(normalizedPath)));
            dirHash = dirHash.TrimEnd('='); // base64 url encode it.
            dirHash = dirHash.Replace('+', '-');
            dirHash = dirHash.Replace('/', '_');
            return dirHash;
        }
    }

    public T MutexExec<T>(Func<T> function, string mutexName)
    {
        using var mutex = new Mutex(false, mutexName);
        bool hasMutex = false;

        try
        {
            try
            {
                mutex.WaitOne();
            }
            catch (AbandonedMutexException)
            {
            }

            hasMutex = true;
            return function();
        }
        finally
        {
            if (hasMutex)
            {
                mutex.ReleaseMutex();
            }
        }
    }

    public T MutexExec<T>(Func<Task<T>> function, string mutexName) =>
        MutexExec(() => function().GetAwaiter().GetResult(), mutexName); // Can't await because of mutex

    // This overload is here so that async Actions don't get routed to MutexExec<T>(Func<T> function, string path)
    public void MutexExec(Func<Task> function, string mutexName) =>
        MutexExec(() => { function().GetAwaiter().GetResult(); return true; }, mutexName);

    public T DirectoryMutexExec<T>(Func<T> function, string path) =>
        MutexExec(function, $"Global\\{ComputeSha256Hash(path)}");

    public T DirectoryMutexExec<T>(Func<Task<T>> function, string path) =>
        DirectoryMutexExec(() => function().GetAwaiter().GetResult(), path); // Can't await because of mutex

    // This overload is here so that async Actions don't get routed to DirectoryMutexExec<T>(Func<T> function, string path)
    public void DirectoryMutexExec(Func<Task> function, string path) => 
        DirectoryMutexExec(() => { function().GetAwaiter().GetResult(); return true; }, path);
}

public static class KeyValuePairExtensions
{
    public static void Deconstruct<TKey, TValue>(this KeyValuePair<TKey, TValue> pair, out TKey key, out TValue value) { key = pair.Key; value = pair.Value; }
}