/// <summary>
        /// Probes whether the object corresponding to the key is in the cache.
        /// Note that the act of probing touches the item (if present in cache),
        /// thus changing its LRU timestamp.
        /// <para />
        /// This will be faster than retrieving the object, but it still has
        /// file system accesses and should NOT be called on the UI thread.
        /// </summary>
        /// <param name="key">The key to check.</param>
        /// <returns>Whether the keyed mValue is in the cache.</returns>
        public bool Probe(ICacheKey key)
        {
            string resourceId = null;

            try
            {
                lock (_lock)
                {
                    IList <string> resourceIds = CacheKeyUtil.GetResourceIds(key);
                    foreach (var entry in resourceIds)
                    {
                        resourceId = entry;
                        if (_storage.Touch(resourceId, key))
                        {
                            _resourceIndex.Add(resourceId);
                            return(true);
                        }
                    }

                    return(false);
                }
            }
            catch (IOException e)
            {
                SettableCacheEvent cacheEvent = SettableCacheEvent.Obtain()
                                                .SetCacheKey(key)
                                                .SetResourceId(resourceId)
                                                .SetException(e);
                _cacheEventListener.OnReadException(cacheEvent);
                cacheEvent.Recycle();
                return(false);
            }
        }
        /// <summary>
        /// Deletes old cache files.
        /// </summary>
        /// <param name="cacheExpirationMs">
        /// Files older than this will be deleted.
        /// </param>
        /// <returns>
        /// The age in ms of the oldest file remaining in the cache.
        /// </returns>
        public long ClearOldEntries(long cacheExpirationMs)
        {
            long oldestRemainingEntryAgeMs = 0L;

            lock (_lock)
            {
                try
                {
                    DateTime             now        = _clock.Now;
                    ICollection <IEntry> allEntries = _storage.GetEntries();
                    long cacheSizeBeforeClearance   = _cacheStats.Size;
                    int  itemsRemovedCount          = 0;
                    long itemsRemovedSize           = 0L;
                    foreach (IEntry entry in allEntries)
                    {
                        // Entry age of zero is disallowed.
                        long entryAgeMs = Math.Max(1, Math.Abs((long)(now - entry.Timestamp).TotalMilliseconds));
                        if (entryAgeMs >= cacheExpirationMs)
                        {
                            long entryRemovedSize = _storage.Remove(entry);
                            _resourceIndex.Remove(entry.Id);
                            if (entryRemovedSize > 0)
                            {
                                itemsRemovedCount++;
                                itemsRemovedSize += entryRemovedSize;
                                SettableCacheEvent cacheEvent = SettableCacheEvent.Obtain()
                                                                .SetResourceId(entry.Id)
                                                                .SetEvictionReason(EvictionReason.CONTENT_STALE)
                                                                .SetItemSize(entryRemovedSize)
                                                                .SetCacheSize(cacheSizeBeforeClearance - itemsRemovedSize);
                                _cacheEventListener.OnEviction(cacheEvent);
                                cacheEvent.Recycle();
                            }
                        }
                        else
                        {
                            oldestRemainingEntryAgeMs = Math.Max(oldestRemainingEntryAgeMs, entryAgeMs);
                        }
                    }

                    _storage.PurgeUnexpectedResources();
                    if (itemsRemovedCount > 0)
                    {
                        MaybeUpdateFileCacheSize();
                        _cacheStats.Increment(-itemsRemovedSize, -itemsRemovedCount);
                    }
                }
                catch (IOException ioe)
                {
                    _cacheErrorLogger.LogError(
                        CacheErrorCategory.EVICTION,
                        typeof(DiskStorageCache),
                        "clearOldEntries: " + ioe.Message);
                }
            }

            return(oldestRemainingEntryAgeMs);
        }
        /// <summary>
        /// Inserts resource into file with key.
        /// </summary>
        /// <param name="key">Cache key.</param>
        /// <param name="callback">
        /// Callback that writes to an output stream.
        /// </param>
        /// <returns>A sequence of bytes.</returns>
        public IBinaryResource Insert(ICacheKey key, IWriterCallback callback)
        {
            // Write to a temp file, then move it into place.
            // This allows more parallelism when writing files.
            SettableCacheEvent cacheEvent = SettableCacheEvent.Obtain().SetCacheKey(key);

            _cacheEventListener.OnWriteAttempt(cacheEvent);
            string resourceId;

            lock (_lock)
            {
                // For multiple resource ids associated with the same image,
                // we only write one file
                resourceId = CacheKeyUtil.GetFirstResourceId(key);
            }

            cacheEvent.SetResourceId(resourceId);

            try
            {
                // Getting the file is synchronized
                IInserter inserter = StartInsert(resourceId, key);

                try
                {
                    inserter.WriteData(callback, key);

                    // Committing the file is synchronized
                    IBinaryResource resource = EndInsert(inserter, key, resourceId);
                    cacheEvent.SetItemSize(resource.GetSize())
                    .SetCacheSize(_cacheStats.Size);

                    _cacheEventListener.OnWriteSuccess(cacheEvent);
                    return(resource);
                }
                finally
                {
                    if (!inserter.CleanUp())
                    {
                        Debug.WriteLine("Failed to delete temp file");
                    }
                }
            }
            catch (IOException ioe)
            {
                cacheEvent.SetException(ioe);
                _cacheEventListener.OnWriteException(cacheEvent);
                Debug.WriteLine("Failed inserting a file into the cache");
                throw;
            }
            finally
            {
                cacheEvent.Recycle();
            }
        }
        /// <summary>
        /// Retrieves the file corresponding to the key, if it is in the cache.
        /// Also touches the item, thus changing its LRU timestamp. If the file
        /// is not present in the file cache, returns null.
        /// <para />
        /// This should NOT be called on the UI thread.
        /// </summary>
        /// <param name="key">The key to check.</param>
        /// <returns>
        /// The resource if present in cache, otherwise null.
        /// </returns>
        public IBinaryResource GetResource(ICacheKey key)
        {
            SettableCacheEvent cacheEvent = SettableCacheEvent
                                            .Obtain()
                                            .SetCacheKey(key);

            try
            {
                lock (_lock)
                {
                    IBinaryResource resource    = null;
                    IList <string>  resourceIds = CacheKeyUtil.GetResourceIds(key);
                    string          resourceId  = default(string);
                    foreach (var entry in resourceIds)
                    {
                        resourceId = entry;
                        cacheEvent.SetResourceId(resourceId);
                        resource = _storage.GetResource(resourceId, key);
                        if (resource != null)
                        {
                            break;
                        }
                    }

                    if (resource == null)
                    {
                        _cacheEventListener.OnMiss(cacheEvent);
                        _resourceIndex.Remove(resourceId);
                    }
                    else
                    {
                        _cacheEventListener.OnHit(cacheEvent);
                        _resourceIndex.Add(resourceId);
                    }

                    return(resource);
                }
            }
            catch (IOException ioe)
            {
                _cacheErrorLogger.LogError(
                    CacheErrorCategory.GENERIC_IO,
                    typeof(DiskStorageCache),
                    "GetResource");
                cacheEvent.SetException(ioe);
                _cacheEventListener.OnReadException(cacheEvent);
                return(null);
            }
            finally
            {
                cacheEvent.Recycle();
            }
        }
        private void EvictAboveSize(
            long desiredSize,
            EvictionReason reason)
        {
            ICollection <IEntry> entries;

            try
            {
                entries = GetSortedEntries(_storage.GetEntries());
            }
            catch (IOException ioe)
            {
                _cacheErrorLogger.LogError(
                    CacheErrorCategory.EVICTION,
                    typeof(DiskStorageCache),
                    "evictAboveSize: " + ioe.Message);

                throw;
            }

            long cacheSizeBeforeClearance = _cacheStats.Size;
            long deleteSize   = cacheSizeBeforeClearance - desiredSize;
            int  itemCount    = 0;
            long sumItemSizes = 0L;

            foreach (var entry in entries)
            {
                if (sumItemSizes > (deleteSize))
                {
                    break;
                }

                long deletedSize = _storage.Remove(entry);
                _resourceIndex.Remove(entry.Id);
                if (deletedSize > 0)
                {
                    itemCount++;
                    sumItemSizes += deletedSize;
                    SettableCacheEvent cacheEvent = SettableCacheEvent.Obtain()
                                                    .SetResourceId(entry.Id)
                                                    .SetEvictionReason(reason)
                                                    .SetItemSize(deletedSize)
                                                    .SetCacheSize(cacheSizeBeforeClearance - sumItemSizes)
                                                    .SetCacheLimit(desiredSize);
                    _cacheEventListener.OnEviction(cacheEvent);
                    cacheEvent.Recycle();
                }
            }

            _cacheStats.Increment(-sumItemSizes, -itemCount);
            _storage.PurgeUnexpectedResources();
        }