/// <summary> /// Returns a value indicating whether the original file is new or has been updated. /// </summary> /// <returns> /// True if the the original file is new or has been updated; otherwise, false. /// </returns> internal async Task <bool> IsNewOrUpdatedFileAsync() { string path = this.CachedPath; bool isUpdated = false; CachedImage cachedImage; if (this.isRemote) { cachedImage = await CacheIndexer.GetValueAsync(path); if (cachedImage != null) { // Can't check the last write time so check to see if the cached image is set to expire // or if the max age is different. if (this.IsExpired(cachedImage.CreationTimeUtc)) { CacheIndexer.Remove(path); isUpdated = true; } } else { // Nothing in the cache so we should return true. isUpdated = true; } } else { // Test now for locally requested files. cachedImage = await CacheIndexer.GetValueAsync(path); if (cachedImage != null) { FileInfo imageFileInfo = new FileInfo(this.requestPath); if (imageFileInfo.Exists) { // Pull the latest info. imageFileInfo.Refresh(); // Check to see if the last write time is different of whether the // cached image is set to expire or if the max age is different. if (!this.RoughDateTimeCompare(imageFileInfo.LastWriteTimeUtc, cachedImage.LastWriteTimeUtc) || this.IsExpired(cachedImage.CreationTimeUtc)) { CacheIndexer.Remove(path); isUpdated = true; } } } else { // Nothing in the cache so we should return true. isUpdated = true; } } return(isUpdated); }
/// <summary> /// Gets a value indicating whether the image is new or updated in an asynchronous manner. /// </summary> /// <returns> /// The <see cref="Task"/>. /// </returns> public override async Task <bool> IsNewOrUpdatedAsync() { // TODO: Before this check is performed it should be throttled. For example, only perform this check // if the last time it was checked is greater than 5 seconds. This would be much better for perf // if there is a high throughput of image requests. string cachedFileName = await this.CreateCachedFileNameAsync(); // Collision rate of about 1 in 10000 for the folder structure. // That gives us massive scope to store millions of files. string pathFromKey = string.Join("\\", cachedFileName.ToCharArray().Take(6)); string virtualPathFromKey = pathFromKey.Replace(@"\", "/"); this.CachedPath = Path.Combine(this.absoluteCachePath, pathFromKey, cachedFileName); this.virtualCachedFilePath = Path.Combine(this.virtualCachePath, virtualPathFromKey, cachedFileName).Replace(@"\", "/"); bool isUpdated = false; CachedImage cachedImage = CacheIndexer.Get(this.CachedPath); if (cachedImage == null) { FileInfo fileInfo = new FileInfo(this.CachedPath); if (fileInfo.Exists) { // Pull the latest info. fileInfo.Refresh(); cachedImage = new CachedImage { Key = Path.GetFileNameWithoutExtension(this.CachedPath), Path = this.CachedPath, CreationTimeUtc = fileInfo.CreationTimeUtc }; CacheIndexer.Add(cachedImage); } } if (cachedImage == null) { // Nothing in the cache so we should return true. isUpdated = true; } else { // Check to see if the cached image is set to expire. if (this.IsExpired(cachedImage.CreationTimeUtc)) { CacheIndexer.Remove(this.CachedPath); isUpdated = true; } } return(isUpdated); }
/// <summary> /// Adds an image to the cache. /// </summary> /// <param name="cachedPath"> /// The path to the cached image. /// </param> public void AddImageToCache(string cachedPath) { string key = Path.GetFileNameWithoutExtension(cachedPath); CachedImage cachedImage = new CachedImage { Key = key, Path = cachedPath, CreationTimeUtc = DateTime.UtcNow }; CacheIndexer.Add(cachedImage); }
/// <summary> /// Gets a value indicating whether the image is new or updated in an asynchronous manner. /// </summary> /// <returns> /// The <see cref="Task"/>. /// </returns> public override async Task <bool> IsNewOrUpdatedAsync() { // TODO: Before this check is performed it should be throttled. For example, only perform this check // if the last time it was checked is greater than 5 seconds. This would be much better for perf // if there is a high throughput of image requests. string cachedFileName = await this.CreateCachedFileNameAsync().ConfigureAwait(false); this.CachedPath = CachedImageHelper.GetCachedPath(this.absoluteCachePath, cachedFileName, false, this.FolderDepth); this.virtualCachedFilePath = CachedImageHelper.GetCachedPath(this.virtualCachePath, cachedFileName, true, this.FolderDepth); bool isUpdated = false; CachedImage cachedImage = CacheIndexer.Get(this.CachedPath); if (cachedImage == null) { var info = new FileInfo(this.CachedPath); if (info.Exists) { cachedImage = new CachedImage { Key = Path.GetFileNameWithoutExtension(this.CachedPath), Path = this.CachedPath, CreationTimeUtc = info.LastWriteTimeUtc }; CacheIndexer.Add(cachedImage, this.ImageCacheMaxMinutes); } } if (cachedImage == null) { // Nothing in the cache so we should return true. isUpdated = true; } else { // Check to see if the cached image is set to expire // or a new file with the same name has replaced our current image if (this.IsExpired(cachedImage.CreationTimeUtc) || await this.IsUpdatedAsync(cachedImage.CreationTimeUtc).ConfigureAwait(false)) { CacheIndexer.Remove(this.CachedPath); isUpdated = true; } else { // Set cachedImageCreationTimeUtc so we can sender Last-Modified or ETag header when using Response.TransmitFile() this.cachedImageCreationTimeUtc = cachedImage.CreationTimeUtc; } } return(isUpdated); }
/// <summary> /// Gets the <see cref="T:System.DateTime"/> set to the last write time of the file. /// </summary> /// <returns> /// The last write time of the file. /// </returns> internal async Task <DateTime> GetLastWriteTimeAsync() { DateTime dateTime = DateTime.UtcNow; CachedImage cachedImage = await CacheIndexer.GetValueAsync(this.CachedPath); if (cachedImage != null) { dateTime = cachedImage.LastWriteTimeUtc; } return(dateTime); }
/// <summary> /// Adds an image to the cache. /// </summary> /// <param name="creationAndLastWriteDateTimes"> /// The creation and last write times. /// </param> internal void AddImageToCache(Tuple <DateTime, DateTime> creationAndLastWriteDateTimes) { string key = Path.GetFileNameWithoutExtension(this.CachedPath); CachedImage cachedImage = new CachedImage { Key = key, Path = this.CachedPath, CreationTimeUtc = creationAndLastWriteDateTimes.Item1, LastWriteTimeUtc = creationAndLastWriteDateTimes.Item2 }; CacheIndexer.Add(cachedImage); }
/// <summary> /// Trims the cache of any expired items in an asynchronous manner. /// </summary> /// <returns> /// The asynchronous <see cref="Task"/> representing an asynchronous operation. /// </returns> public override async Task TrimCacheAsync() { string directory = Path.GetDirectoryName(this.CachedPath); if (directory != null) { DirectoryInfo directoryInfo = new DirectoryInfo(directory); DirectoryInfo parentDirectoryInfo = directoryInfo.Parent; if (parentDirectoryInfo != null) { // UNC folders can throw exceptions if the file doesn't exist. foreach (DirectoryInfo enumerateDirectory in await parentDirectoryInfo.SafeEnumerateDirectoriesAsync()) { IEnumerable <FileInfo> files = enumerateDirectory.EnumerateFiles().OrderBy(f => f.CreationTimeUtc); int count = files.Count(); foreach (FileInfo fileInfo in files) { try { // If the group count is equal to the max count minus 1 then we know we // have reduced the number of items below the maximum allowed. // We'll cleanup any orphaned expired files though. if (!this.IsExpired(fileInfo.CreationTimeUtc) && count <= MaxFilesCount - 1) { break; } // Remove from the cache and delete each CachedImage. CacheIndexer.Remove(fileInfo.Name); fileInfo.Delete(); count -= 1; } // ReSharper disable once EmptyGeneralCatchClause catch { // Log it but skip to the next file. ImageProcessorBootstrapper.Instance.Logger.Log <DiskCache>("Unable to clean cached file: " + fileInfo.FullName); } } } } } }
/// <summary> /// Trims a cached folder ensuring that it does not exceed the maximum file count. /// </summary> /// <param name="path"> /// The path to the folder. /// </param> public static void TrimCachedFolders(string path) { string directory = Path.GetDirectoryName(path); if (directory != null) { DirectoryInfo directoryInfo = new DirectoryInfo(directory); DirectoryInfo parentDirectoryInfo = directoryInfo.Parent; if (parentDirectoryInfo != null) { // UNC folders can throw exceptions if the file doesn't exist. foreach (DirectoryInfo enumerateDirectory in parentDirectoryInfo.SafeEnumerateDirectories()) { IEnumerable <FileInfo> files = enumerateDirectory.EnumerateFiles().OrderBy(f => f.CreationTimeUtc); int count = files.Count(); foreach (FileInfo fileInfo in files) { try { // If the group count is equal to the max count minus 1 then we know we // have reduced the number of items below the maximum allowed. // We'll cleanup any orphaned expired files though. if (!IsExpired(fileInfo.CreationTimeUtc) && count <= MaxFilesCount - 1) { break; } // Remove from the cache and delete each CachedImage. CacheIndexer.Remove(fileInfo.Name); fileInfo.Delete(); count -= 1; } // ReSharper disable once EmptyGeneralCatchClause catch { // Do nothing; skip to the next file. } } } } } }
/// <summary> /// Returns a value indicating whether the original file is new or has been updated. /// </summary> /// <param name="cachedPath"> /// The path to the cached image. /// </param> /// <returns> /// True if The original file is new or has been updated; otherwise, false. /// </returns> public bool IsNewOrUpdatedFile(string cachedPath) { bool isUpdated = false; CachedImage cachedImage = CacheIndexer.GetValue(cachedPath); if (cachedImage == null) { // Nothing in the cache so we should return true. isUpdated = true; } else { // Check to see if the cached image is set to expire. if (IsExpired(cachedImage.CreationTimeUtc)) { CacheIndexer.Remove(cachedPath); isUpdated = true; } } return(isUpdated); }
/// <summary> /// Trims a cached folder ensuring that it does not exceed the maximum file count. /// </summary> /// <param name="path"> /// The path to the folder. /// </param> private void TrimCachedFolders(string path) { // ReSharper disable once AssignNullToNotNullAttribute DirectoryInfo directoryInfo = new DirectoryInfo(Path.GetDirectoryName(path)); DirectoryInfo parentDirectoryInfo = directoryInfo.Parent; // ReSharper disable once PossibleNullReferenceException foreach (DirectoryInfo enumerateDirectory in parentDirectoryInfo.EnumerateDirectories()) { IEnumerable <FileInfo> files = enumerateDirectory.EnumerateFiles().OrderBy(f => f.CreationTimeUtc); int count = files.Count(); foreach (FileInfo fileInfo in files) { try { // If the group count is equal to the max count minus 1 then we know we // have reduced the number of items below the maximum allowed. // We'll cleanup any orphaned expired files though. if (!this.IsExpired(fileInfo.CreationTimeUtc) && count <= MaxFilesCount - 1) { break; } // Remove from the cache and delete each CachedImage. CacheIndexer.Remove(fileInfo.Name); fileInfo.Delete(); count -= 1; } // ReSharper disable once EmptyGeneralCatchClause catch { // Do nothing; skip to the next file. } } } }
/// <summary> /// Trims the cache of any expired items in an asynchronous manner. /// </summary> /// <returns> /// The asynchronous <see cref="Task"/> representing an asynchronous operation. /// </returns> public override Task TrimCacheAsync() { if (!this.TrimCache) { return(Task.FromResult(0)); } this.ScheduleCacheTrimmer(token => { var rootDirectory = Path.GetDirectoryName(this.CachedPath); if (rootDirectory != null) { // Jump up to the parent branch to clean through the cache // UNC folders can throw exceptions if the file doesn't exist var directories = SafeEnumerateDirectories(validatedAbsoluteCachePath).Reverse().ToList(); foreach (string directory in directories) { if (token.IsCancellationRequested) { break; } try { var files = Directory.EnumerateFiles(directory).Select(f => new FileInfo(f)).OrderBy(f => f.CreationTimeUtc).ToList(); var count = files.Count; foreach (var fileInfo in files) { if (token.IsCancellationRequested) { break; } try { // If the group count is equal to the max count minus 1 then we know we have reduced the number of items below the maximum allowed // We'll cleanup any orphaned expired files though if (!this.IsExpired(fileInfo.CreationTimeUtc) && count <= MaxFilesCount - 1) { break; } // Remove from the cache and delete each CachedImage CacheIndexer.Remove(fileInfo.Name); fileInfo.Delete(); count--; } catch (Exception ex) { // Log it but skip to the next file ImageProcessorBootstrapper.Instance.Logger.Log <DiskCache>($"Unable to clean cached file: {fileInfo.FullName}, {ex.Message}"); } } // If the directory is empty, delete it to remove the FCN if (count == 0 && Directory.GetDirectories(directory, "*", SearchOption.TopDirectoryOnly).Length == 0) { Directory.Delete(directory); } } catch (Exception ex) { // Log it but skip to the next directory ImageProcessorBootstrapper.Instance.Logger.Log <DiskCache>($"Unable to clean cached directory: {directory}, {ex.Message}"); } } } return(Task.FromResult(0)); }); return(Task.FromResult(0)); }
/// <summary> /// Trims the cache of any expired items in an asynchronous manner. /// </summary> /// <returns> /// The asynchronous <see cref="Task"/> representing an asynchronous operation. /// </returns> public override Task TrimCacheAsync() { if (!this.TrimCache) { return(Task.FromResult(0)); } this.ScheduleCacheTrimmer(token => { string rootDirectory = Path.GetDirectoryName(this.CachedPath); if (rootDirectory != null) { // Jump up to the parent branch to clean through the cache. // UNC folders can throw exceptions if the file doesn't exist. IEnumerable <string> directories = SafeEnumerateDirectories(validatedAbsoluteCachePath).Reverse(); foreach (string directory in directories) { if (!Directory.Exists(directory)) { continue; } if (token.IsCancellationRequested) { break; } IEnumerable <FileInfo> files = Directory.EnumerateFiles(directory) .Select(f => new FileInfo(f)) .OrderBy(f => f.CreationTimeUtc); int count = files.Count(); foreach (FileInfo fileInfo in files) { if (token.IsCancellationRequested) { break; } try { // If the group count is equal to the max count minus 1 then we know we // have reduced the number of items below the maximum allowed. // We'll cleanup any orphaned expired files though. if (!this.IsExpired(fileInfo.CreationTimeUtc) && count <= MaxFilesCount - 1) { break; } // Remove from the cache and delete each CachedImage. CacheIndexer.Remove(fileInfo.Name); fileInfo.Delete(); count--; } catch (Exception ex) { // Log it but skip to the next file. ImageProcessorBootstrapper.Instance.Logger.Log <DiskCache>($"Unable to clean cached file: {fileInfo.FullName}, {ex.Message}"); } } // If the directory is empty of files delete it to remove the FCN. this.RecursivelyDeleteEmptyDirectories(directory, validatedAbsoluteCachePath, token); } } return(Task.FromResult(0)); }); return(Task.FromResult(0)); }