private static async Task <ImmutableDictionary <HttpRequestMessage, HttpResponseMessage> > ReadCacheAsync(string fileName) { var result = ImmutableDictionary.CreateBuilder <HttpRequestMessage, HttpResponseMessage>(HttpRequestEqualityComparer.Default); DateTime lastWriteTimeUtc; using (var cacheStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, useAsync: true)) { lastWriteTimeUtc = File.GetLastWriteTimeUtc(fileName); await VerifyFileHeaderAsync(cacheStream); while (cacheStream.Position < cacheStream.Length) { var request = await HttpMessageSerializer.DeserializeRequestAsync(cacheStream); if (request == null) { break; } var response = await HttpMessageSerializer.DeserializeResponseAsync(cacheStream); result[request] = response; // Allow for extra line endings after the response for readability. SkipBlankLines(cacheStream); } if (result.Count == 0) { throw new BadCacheFileException("No cached responses found."); } } return(result.ToImmutable()); }
/// <summary> /// Serializes the cache out to a file in the specified directory. /// </summary> /// <param name="updateLocation">The path to the directory to write new or updated cache entries to. May be null.</param> /// <returns>A task that tracks completion of the operation.</returns> internal async Task PersistCacheAsync(string updateLocation) { Requires.NotNullOrEmpty(updateLocation, nameof(updateLocation)); Verify.Operation(this.cacheFilePath != null, "This instance cannot be persisted because it was not created with a path."); // We don't want to write files to the source directory of the test project if the test project isn't even there. string updateLocationNoTrailingSlash = updateLocation.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); Verify.Operation(Directory.GetParent(updateLocationNoTrailingSlash).Exists, "Caching an HTTP response to \"{0}\" requires that its parent directory already exist. Is the source code for the test not on this machine?", updateLocation); // Get in line to write to the cache so we avoid file conflicts. // We don't need to queue up a long line of redundant file saves, // so only queue one if no one is already waiting to start saving. if (Interlocked.Exchange(ref this.saveQueued, 1) == 0) { await this.savingCacheSemaphore.WaitAsync(); try { Directory.CreateDirectory(updateLocation); string filePathToUpdate = Path.Combine(updateLocation, Path.GetFileName(this.cacheFilePath)); using (var fileStream = new FileStream(filePathToUpdate, FileMode.Create, FileAccess.Write, FileShare.None, 4096, useAsync: true)) { await fileStream.WriteAsync(FileHeader, 0, FileHeader.Length); // Snap the memory cache to save. But first, clear the saveQueued field so if anyone wants to persist changes made after this, they will queue themselves. Volatile.Write(ref this.saveQueued, 0); #if !NETSTANDARD1_3 Thread.MemoryBarrier(); #endif var cacheDictionary = await Volatile.Read(ref this.cacheDictionary); foreach (var entry in cacheDictionary) { await HttpMessageSerializer.SerializeAsync(entry.Key, fileStream); await HttpMessageSerializer.SerializeAsync(entry.Value, fileStream); await fileStream.WriteAsync(SpaceBetweenResponseAndRequest, 0, SpaceBetweenResponseAndRequest.Length); } } // Protect against git normalizing line endings for this file. await this.WriteGitAttributesFileAsync(updateLocation); } finally { this.savingCacheSemaphore.Release(); } } }