public async Task CanWriteAndReadAFile(FileArtifactType type) { // Arrange var expected = new MemoryStream(Encoding.UTF8.GetBytes("Hello, world!")); // Act & Assert await _target.StoreStreamAsync( Id, Version, type, dest => expected.CopyToAsync(dest)); _blobStorageService.Verify( x => x.UploadStreamAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <Stream>()), Times.Once); _blobStorageService.Verify( x => x.GetStreamOrNullAsync(It.IsAny <string>()), Times.Never); _blobStorageService.ResetCalls(); using (var actual = await _target.GetStreamOrNullAsync(Id, Version, type)) { AssertSameStreams(expected, actual); _blobStorageService.Verify( x => x.UploadStreamAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <Stream>()), Times.Never); _blobStorageService.Verify( x => x.GetStreamOrNullAsync(It.IsAny <string>()), Times.Never); } }
private static IEnumerable <string> EnumerateFileOrDirectories( string directoryPath, FileArtifactType fileArtifactType, string searchPattern, SearchOption searchOption = SearchOption.TopDirectoryOnly) { var enumeration = new List <string>(); // The search pattern and path gets normalized so we always use backslashes searchPattern = NormalizePathToWindowsStyle(searchPattern); directoryPath = NormalizePathToWindowsStyle(directoryPath); var result = CustomEnumerateDirectoryEntries( directoryPath, fileArtifactType, searchPattern, searchOption, enumeration); // If the result indicates that the enumeration succeeded or the directory does not exist, then the result is considered success. // In particular, if the globed directory does not exist, then we want to return the empty file, and track for the anti-dependency. if ( !(result.Status == WindowsNative.EnumerateDirectoryStatus.Success || result.Status == WindowsNative.EnumerateDirectoryStatus.SearchDirectoryNotFound)) { throw result.CreateExceptionForError(); } return(enumeration); }
public async Task <Stream> GetStreamOrNullAsync(string id, string version, FileArtifactType type) { var cacheKey = GetCacheKey(id, version, type); if (_memoryCache.TryGetValue <byte[]>(cacheKey, out var cachedValue)) { return(new MemoryStream(cachedValue)); } var blobName = _blobNameProvider.GetLatestBlobName(id, version, type); var outputStream = new MemoryStream(); using (var blobStream = await _blobStorageService.GetStreamOrNullAsync(blobName)) { if (blobStream == null) { return(null); } await blobStream.CopyToAsync(outputStream); } CacheMemoryStream(cacheKey, outputStream); outputStream.Position = 0; return(outputStream); }
public async Task StoreStreamAsync(string id, string version, FileArtifactType type, Func <Stream, Task> writeAsync) { using (var memoryStream = new MemoryStream()) { await writeAsync(memoryStream); var blobName = _blobNameProvider.GetLatestBlobName(id, version, type); var contentType = GetContentType(type); memoryStream.Position = 0; var cacheKey = GetCacheKey(id, version, type); try { await _blobStorageService.UploadStreamAsync(blobName, contentType, memoryStream); } catch { // Clear the cache since we don't know what happened. _memoryCache.Remove(cacheKey); throw; } CacheMemoryStream(cacheKey, memoryStream); } }
public string GetLatestBlobName(string id, string version, FileArtifactType type) { var lowerId = id.ToLowerInvariant(); var lowerVersion = version.ToLowerInvariant(); var extension = GetExtension(type); var blobName = $"{lowerId}/{lowerVersion}/latest.{extension}"; return(blobName); }
public async Task ReturnsNullWithNonExistentFile(FileArtifactType type) { // Arrange & Act using (var actual = await _target.GetStreamOrNullAsync(Id, Version, type)) { // Assert Assert.Null(actual); _blobStorageService.Verify( x => x.UploadStreamAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <Stream>()), Times.Never); _blobStorageService.Verify( x => x.GetStreamOrNullAsync(It.IsAny <string>()), Times.Once); } }
private static string GetExtension(FileArtifactType type) { string extension; switch (type) { case FileArtifactType.Nuspec: extension = "nuspec"; break; case FileArtifactType.MZip: extension = "mzip"; break; default: throw new NotSupportedException($"The file artifact type {type} is not supported."); } return(extension); }
private static string GetContentType(FileArtifactType type) { string contentType; switch (type) { case FileArtifactType.Nuspec: contentType = "application/xml"; break; case FileArtifactType.MZip: contentType = "application/octet-stream"; break; default: throw new NotSupportedException($"The file artifact type {type} is not supported."); } return(contentType); }
private static bool FileOrDirectoryExists(FileArtifactType fileArtifactType, string path) { // The path gets normalized so we always use backslashes path = NormalizePathToWindowsStyle(path); WindowsNative.Win32FindData findResult; using (var findHandle = WindowsNative.FindFirstFileW(path.TrimEnd('\\'), out findResult)) { // Any error is interpreted as a file not found. This matches the managed Directory.Exists and File.Exists behavior if (findHandle.IsInvalid) { return(false); } if (fileArtifactType == FileArtifactType.FileOrDirectory) { return(true); } var isDirectory = (findResult.DwFileAttributes & FileAttributes.Directory) != 0; return(!(fileArtifactType == FileArtifactType.Directory ^ isDirectory)); } }
private static WindowsNative.EnumerateDirectoryResult CustomEnumerateDirectoryEntries( string directoryPath, FileArtifactType fileArtifactType, string pattern, SearchOption searchOption, ICollection <string> result) { var searchDirectoryPath = Path.Combine(directoryPath.TrimEnd('\\'), "*"); WindowsNative.Win32FindData findResult; using (var findHandle = WindowsNative.FindFirstFileW(searchDirectoryPath, out findResult)) { if (findHandle.IsInvalid) { int hr = Marshal.GetLastWin32Error(); Debug.Assert(hr != WindowsNative.ErrorFileNotFound); WindowsNative.EnumerateDirectoryStatus findHandleOpenStatus; switch (hr) { case WindowsNative.ErrorFileNotFound: findHandleOpenStatus = WindowsNative.EnumerateDirectoryStatus.SearchDirectoryNotFound; break; case WindowsNative.ErrorPathNotFound: findHandleOpenStatus = WindowsNative.EnumerateDirectoryStatus.SearchDirectoryNotFound; break; case WindowsNative.ErrorDirectory: findHandleOpenStatus = WindowsNative.EnumerateDirectoryStatus.CannotEnumerateFile; break; case WindowsNative.ErrorAccessDenied: findHandleOpenStatus = WindowsNative.EnumerateDirectoryStatus.AccessDenied; break; default: findHandleOpenStatus = WindowsNative.EnumerateDirectoryStatus.UnknownError; break; } return(new WindowsNative.EnumerateDirectoryResult(directoryPath, findHandleOpenStatus, hr)); } while (true) { var isDirectory = (findResult.DwFileAttributes & FileAttributes.Directory) != 0; // There will be entries for the current and parent directories. Ignore those. if (!isDirectory || (findResult.CFileName != "." && findResult.CFileName != "..")) { // Make sure pattern and directory/file filters are honored // We special case the "*" pattern since it is the default when no pattern is specified // so we avoid calling the matching function if (pattern == "*" || WindowsNative.PathMatchSpecExW(findResult.CFileName, pattern, WindowsNative.DwFlags.PmsfNormal) == WindowsNative.ErrorSuccess) { if (fileArtifactType == FileArtifactType.FileOrDirectory || !(fileArtifactType == FileArtifactType.Directory ^ isDirectory)) { result.Add(Path.Combine(directoryPath, findResult.CFileName)); } } // Recursively go into subfolders if specified if (searchOption == SearchOption.AllDirectories && isDirectory) { var recurs = CustomEnumerateDirectoryEntries( Path.Combine(directoryPath, findResult.CFileName), fileArtifactType, pattern, searchOption, result); if (!recurs.Succeeded) { return(recurs); } } } if (!WindowsNative.FindNextFileW(findHandle, out findResult)) { int hr = Marshal.GetLastWin32Error(); if (hr == WindowsNative.ErrorNoMoreFiles) { // Graceful completion of enumeration. return(new WindowsNative.EnumerateDirectoryResult( directoryPath, WindowsNative.EnumerateDirectoryStatus.Success, hr)); } Debug.Assert(hr != WindowsNative.ErrorSuccess); return(new WindowsNative.EnumerateDirectoryResult( directoryPath, WindowsNative.EnumerateDirectoryStatus.UnknownError, hr)); } } } }
private static string GetCacheKey(string id, string version, FileArtifactType type) { return($"{id}/{version}/{type}".ToLowerInvariant()); }