/// <summary> /// Opens a file stream to the given asset /// </summary> /// <param name="id">The Id of the file</param> /// <param name="hash">The hash of the file</param> /// <returns>A read only file handle to the asset</returns> public FileStream GetReadFileStream(Guid id, string hash) { string path = Path.Combine(this.root, CacheFile.GetFolder(hash), CacheFile.GetFileName(id, hash)); File.SetLastAccessTime(path, DateTime.Now); return(File.OpenRead(path)); }
/// <summary> /// Walks through all assets in the file system and evicts any items that occur in the past /// to take the cache file system below the requested limit. /// This should only be called in a background thread since this is a time intensive operation. /// </summary> /// <param name="state">Ignored, object state</param> private void Evict(object state) { ulong cacheLimitBytes = (ulong)Settings.Default.MaxCacheSizeMB * 1048576; if (this.cacheSizeBytes < cacheLimitBytes) { // Cache isn't big enough to evict logger.Info( "Cache isn't large enough to require eviction. Max: {0} MB, Current: {1} MB", Settings.Default.MaxCacheSizeMB, this.cacheSizeBytes / 1048576); return; } logger.Warn( "Cache eviction is starting to prune. Max: {0} MB, Current: {1} MB", Settings.Default.MaxCacheSizeMB, this.cacheSizeBytes / 1048576); List <CacheFile> fileData = this.EnumerateAllCacheFiles(); // Run the list of elements and delete the files that were accessed the furthest in the past var filesSorted = from a in fileData orderby a.LastAccessed ascending select a; IEnumerator <CacheFile> fileEnumerator = filesSorted.GetEnumerator(); fileEnumerator.MoveNext(); // Delete files until we are within 90% of the limit while (this.cacheSizeBytes > (cacheLimitBytes * Settings.Default.CacheFreePercentage)) { CacheFile file = fileEnumerator.Current; lock (this.cacheSizeBytesLock) { this.cacheSizeBytes -= (ulong)file.Length; } logger.Warn( "Deleting last accessed {1} file {0}", file.LastAccessed, CacheFile.GetFileName(file.Id, file.Hash)); File.Delete(CacheFile.GetFullFilePath(this.root, file.Id, file.Hash)); if (!fileEnumerator.MoveNext()) { // There are no more files to clean break; } } logger.Warn( "Cache eviction is complete. Max: {0} MB, Current: {1} MB", Settings.Default.MaxCacheSizeMB, this.cacheSizeBytes / 1048576); this.evictingCache = false; }
/// <summary> /// Moves the file from the incoming temporary folder and moves it to permanent storage. /// The file is automatically marked as recently accessed in order to prevent it from being evicted. /// </summary> /// <param name="id">The Id of the file</param> /// <param name="hash">The hash of the file</param> public void CompleteFile(Guid id, string hash) { string fileName = CacheFile.GetFileName(id, hash); string src = Path.Combine(this.incoming, fileName); string dest = CacheFile.GetFullFilePath(this.root, id, hash); if (!Directory.Exists(Path.GetDirectoryName(dest))) { Directory.CreateDirectory(Path.GetDirectoryName(dest)); } // For some reason the cache server is asking to overwrite the file, if (CacheFile.IsFileCached(this.root, id, hash)) { File.Delete(CacheFile.GetFullFilePath(this.root, id, hash)); } File.Move(src, dest); File.SetLastAccessTime(dest, DateTime.Now); FileInfo info = new FileInfo(dest); lock (this.cacheSizeBytesLock) { // Increment the cache size by adding a file this.cacheSizeBytes += (ulong)info.Length; int limit = Settings.Default.MaxCacheSizeMB * 1048576; // Check we haven't exceeded the cap if (this.cacheSizeBytes > (ulong)limit && !this.evictingCache) { // We've exceeded the cache cap, request a cleanup this.evictingCache = true; ThreadPool.QueueUserWorkItem(new WaitCallback(this.Evict)); } } // Store a hit on the ojbect File.SetLastAccessTime(dest, DateTime.Now); logger.Info(CultureInfo.CurrentCulture, "Moving {0} to permanent cache", fileName); }
/// <summary> /// Opens a file stream to the file that represents the temporary cache file. The caller should /// close the file and then call CompleteFile to move it to permanent storage. /// </summary> /// <param name="id">The id of the file</param> /// <param name="hash">The hash of the file</param> /// <returns>A file stream to the temporary file</returns> public FileStream GetTemporaryFile(Guid id, string hash) { string path = Path.Combine(this.incoming, CacheFile.GetFileName(id, hash)); return(File.OpenWrite(path)); }
/// <summary> /// Returns the full path of a given file /// </summary> /// <param name="root">The root folder of the cache</param> /// <param name="id">The Id of the file</param> /// <param name="hash">The hash of the file</param> /// <returns>The full path of the file</returns> public static string GetFullFilePath(string root, Guid id, string hash) { return(Path.Combine(root, CacheFile.GetFolder(hash), CacheFile.GetFileName(id, hash))); }