public static HttpCacheResult InitializeHttpCacheResult( string httpCacheDirectory, Uri sourceUri, string cacheKey, HttpSourceCacheContext context) { // When the MaxAge is TimeSpan.Zero, this means the caller is passing is using a temporary directory for // cache files, instead of the global HTTP cache used by default. Additionally, the cleaning up of this // directory is the responsibility of the caller. if (context.MaxAge > TimeSpan.Zero) { var baseFolderName = CachingUtility.RemoveInvalidFileNameChars(CachingUtility.ComputeHash(sourceUri.OriginalString)); var baseFileName = CachingUtility.RemoveInvalidFileNameChars(cacheKey) + ".dat"; var cacheFolder = Path.Combine(httpCacheDirectory, baseFolderName); var cacheFile = Path.Combine(cacheFolder, baseFileName); var newCacheFile = cacheFile + "-new"; return(new HttpCacheResult( context.MaxAge, newCacheFile, cacheFile)); } else { var temporaryFile = Path.Combine(context.RootTempFolder, Path.GetRandomFileName()); var newTemporaryFile = Path.Combine(context.RootTempFolder, Path.GetRandomFileName()); return(new HttpCacheResult( context.MaxAge, newTemporaryFile, temporaryFile)); } }
protected virtual Stream TryReadCacheFile(string uri, TimeSpan maxAge, string cacheFile) { // Do not need the uri here return(CachingUtility.ReadCacheFile(maxAge, cacheFile)); }
public static async Task CreateCacheFileAsync( HttpCacheResult result, HttpResponseMessage response, Action <Stream> ensureValidContents, CancellationToken cancellationToken) { // Get the cache file directories, so we can make sure they are created before writing // files to them. var newCacheFileDirectory = Path.GetDirectoryName(result.NewFile); var cacheFileDirectory = Path.GetDirectoryName(result.CacheFile); // Make sure the new cache file directory is created before writing a file to it. DirectoryUtility.CreateSharedDirectory(newCacheFileDirectory); // The update of a cached file is divided into two steps: // 1) Delete the old file. // 2) Create a new file with the same name. using (var fileStream = new FileStream( result.NewFile, FileMode.Create, FileAccess.ReadWrite, FileShare.None, BufferSize)) { using (var networkStream = await response.Content.ReadAsStreamAsync()) { await networkStream.CopyToAsync(fileStream, BufferSize, cancellationToken); } // Validate the content before putting it into the cache. if (ensureValidContents != null) { fileStream.Seek(0, SeekOrigin.Begin); ensureValidContents.Invoke(fileStream); } } if (File.Exists(result.CacheFile)) { // Process B can perform deletion on an opened file if the file is opened by process A // with FileShare.Delete flag. However, the file won't be actually deleted until A close it. // This special feature can cause race condition, so we never delete an opened file. if (!CachingUtility.IsFileAlreadyOpen(result.CacheFile)) { File.Delete(result.CacheFile); } } // Make sure the cache file directory is created before moving or writing a file to it. if (cacheFileDirectory != newCacheFileDirectory) { DirectoryUtility.CreateSharedDirectory(cacheFileDirectory); } // If the destination file doesn't exist, we can safely perform moving operation. // Otherwise, moving operation will fail. if (!File.Exists(result.CacheFile)) { File.Move( result.NewFile, result.CacheFile); } // Even the file deletion operation above succeeds but the file is not actually deleted, // we can still safely read it because it means that some other process just updated it // and we don't need to update it with the same content again. result.Stream = new FileStream( result.CacheFile, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete, BufferSize); }
public static async Task CreateCacheFileAsync( HttpCacheResult result, HttpResponseMessage response, Action <Stream> ensureValidContents, CancellationToken cancellationToken) { // Get the cache file directories, so we can make sure they are created before writing // files to them. var newCacheFileDirectory = Path.GetDirectoryName(result.NewFile); var cacheFileDirectory = Path.GetDirectoryName(result.CacheFile); // Make sure the new cache file directory is created before writing a file to it. DirectoryUtility.CreateSharedDirectory(newCacheFileDirectory); // The update of a cached file is divided into two steps: // 1) Delete the old file. // 2) Create a new file with the same name. // Some FileStream operations on Windows are synchronous even though it may not seem so. // The HTTP stack rewrite in .NET Core 2.1 introduced circumstances whereby these // synchronous FileStream calls will keep an IO completion thread busy and block other // HTTP requests from completing. The immediate solution is to perform write and read // operations on separate streams, but only on .NET Core where the problem exists. // See https://github.com/dotnet/corefx/issues/31914 for details. const int writeBufferSize = #if IS_CORECLR 1; // This disables write buffering. #else BufferSize; #endif using (var fileStream = new FileStream( result.NewFile, FileMode.Create, FileAccess.Write, FileShare.None, writeBufferSize, useAsync: true)) { using (var networkStream = await response.Content.ReadAsStreamAsync()) { await networkStream.CopyToAsync(fileStream, BufferSize, cancellationToken); } } using (var fileStream = new FileStream( result.NewFile, FileMode.Open, FileAccess.Read, FileShare.None, BufferSize, useAsync: true)) { // Validate the content before putting it into the cache. ensureValidContents?.Invoke(fileStream); } if (File.Exists(result.CacheFile)) { // Process B can perform deletion on an opened file if the file is opened by process A // with FileShare.Delete flag. However, the file won't be actually deleted until A close it. // This special feature can cause race condition, so we never delete an opened file. if (!CachingUtility.IsFileAlreadyOpen(result.CacheFile)) { File.Delete(result.CacheFile); } } // Make sure the cache file directory is created before moving or writing a file to it. if (cacheFileDirectory != newCacheFileDirectory) { DirectoryUtility.CreateSharedDirectory(cacheFileDirectory); } // If the destination file doesn't exist, we can safely perform moving operation. // Otherwise, moving operation will fail. if (!File.Exists(result.CacheFile)) { File.Move( result.NewFile, result.CacheFile); } // Even the file deletion operation above succeeds but the file is not actually deleted, // we can still safely read it because it means that some other process just updated it // and we don't need to update it with the same content again. result.Stream = new FileStream( result.CacheFile, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete, BufferSize, useAsync: true); }