File: Helpers.cs
Web Access
Project: src\src\Common\Microsoft.Arcade.Common\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; }
    }
}