| File: ShadowCopyManager.cs | Web Access |
| Project: src\sdk\src\RazorSdk\Tool\Microsoft.NET.Sdk.Razor.Tool.csproj (rzc) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #nullable disable using System.Globalization; namespace Microsoft.NET.Sdk.Razor.Tool { // Note that this class has no thread-safety guarantees. The caller should use a lock // if concurrency is required. internal class ShadowCopyManager : IDisposable { // Note that this class uses the *existence* of the Mutex to lock a directory. // // Nothing in this code actually ever acquires the Mutex, we just try to see if it exists // already. private readonly Mutex _mutex; private int _counter; public ShadowCopyManager(string baseDirectory = null) { BaseDirectory = baseDirectory ?? Path.Combine(Path.GetTempPath(), "Razor", "ShadowCopy"); var guid = Guid.NewGuid().ToString("N").ToLowerInvariant(); UniqueDirectory = Path.Combine(BaseDirectory, guid); _mutex = new Mutex(initiallyOwned: false, name: guid); Directory.CreateDirectory(UniqueDirectory); } public string BaseDirectory { get; } public string UniqueDirectory { get; } public string AddAssembly(string filePath) { var assemblyDirectory = CreateUniqueDirectory(); var destination = Path.Combine(assemblyDirectory, Path.GetFileName(filePath)); CopyFile(filePath, destination); var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePath); var resourcesNameWithoutExtension = fileNameWithoutExtension + ".resources"; var resourcesNameWithExtension = resourcesNameWithoutExtension + ".dll"; foreach (var directory in Directory.EnumerateDirectories(Path.GetDirectoryName(filePath))) { var directoryName = Path.GetFileName(directory); var resourcesPath = Path.Combine(directory, resourcesNameWithExtension); if (File.Exists(resourcesPath)) { var resourcesShadowCopyPath = Path.Combine(assemblyDirectory, directoryName, resourcesNameWithExtension); CopyFile(resourcesPath, resourcesShadowCopyPath); } resourcesPath = Path.Combine(directory, resourcesNameWithoutExtension, resourcesNameWithExtension); if (File.Exists(resourcesPath)) { var resourcesShadowCopyPath = Path.Combine(assemblyDirectory, directoryName, resourcesNameWithoutExtension, resourcesNameWithExtension); CopyFile(resourcesPath, resourcesShadowCopyPath); } } return destination; } public void Dispose() { _mutex.ReleaseMutex(); } public Task PurgeUnusedDirectoriesAsync() { return Task.Run((Action)PurgeUnusedDirectories); } private string CreateUniqueDirectory() { var id = _counter++; var directory = Path.Combine(UniqueDirectory, id.ToString(CultureInfo.InvariantCulture)); Directory.CreateDirectory(directory); return directory; } private void CopyFile(string originalPath, string shadowCopyPath) { var directory = Path.GetDirectoryName(shadowCopyPath); Directory.CreateDirectory(directory); File.Copy(originalPath, shadowCopyPath); MakeWritable(new FileInfo(shadowCopyPath)); } private void MakeWritable(string directoryPath) { var directory = new DirectoryInfo(directoryPath); foreach (var file in directory.EnumerateFiles(searchPattern: "*", searchOption: SearchOption.AllDirectories)) { MakeWritable(file); } } private void MakeWritable(FileInfo file) { try { if (file.IsReadOnly) { file.IsReadOnly = false; } } catch { // There are many reasons this could fail. Ignore it and keep going. } } private void PurgeUnusedDirectories() { IEnumerable<string> directories; try { directories = Directory.EnumerateDirectories(BaseDirectory); } catch (DirectoryNotFoundException) { return; } foreach (var directory in directories) { Mutex mutex = null; try { // We only want to try deleting the directory if no-one else is currently using it. // // Note that the mutex name is the name of the directory. This is OK because we're using // GUIDs as directory/mutex names. if (!Mutex.TryOpenExisting(Path.GetFileName(directory).ToLowerInvariant(), out mutex)) { MakeWritable(directory); Directory.Delete(directory, recursive: true); } } catch { // If something goes wrong we will leave it to the next run to clean up. // Just swallow the exception and move on. } finally { if (mutex != null) { mutex.Dispose(); } } } } } }