<commit_before>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.cache;

import bolts.Task;
import com.facebook.binaryresource.BinaryResource;
import com.facebook.cache.common.CacheKey;
import com.facebook.cache.common.WriterCallback;
import com.facebook.cache.disk.FileCache;
import com.facebook.common.internal.Preconditions;
import com.facebook.common.logging.FLog;
import com.facebook.common.memory.PooledByteBuffer;
import com.facebook.common.memory.PooledByteBufferFactory;
import com.facebook.common.memory.PooledByteStreams;
import com.facebook.common.references.CloseableReference;
import com.facebook.imagepipeline.image.EncodedImage;
import com.facebook.imagepipeline.systrace.FrescoSystrace;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nullable;

/**
 * BufferedDiskCache provides get and put operations to take care of scheduling disk-cache
 * read/writes.
 */
public class BufferedDiskCache {
  private static final Class<?> TAG = BufferedDiskCache.class;

  private final FileCache mFileCache;
  private final PooledByteBufferFactory mPooledByteBufferFactory;
  private final PooledByteStreams mPooledByteStreams;
  private final Executor mReadExecutor;
  private final Executor mWriteExecutor;
  private final StagingArea mStagingArea;
  private final ImageCacheStatsTracker mImageCacheStatsTracker;

  public BufferedDiskCache(
      FileCache fileCache,
      PooledByteBufferFactory pooledByteBufferFactory,
      PooledByteStreams pooledByteStreams,
      Executor readExecutor,
      Executor writeExecutor,
      ImageCacheStatsTracker imageCacheStatsTracker) {
    mFileCache = fileCache;
    mPooledByteBufferFactory = pooledByteBufferFactory;
    mPooledByteStreams = pooledByteStreams;
    mReadExecutor = readExecutor;
    mWriteExecutor = writeExecutor;
    mImageCacheStatsTracker = imageCacheStatsTracker;
    mStagingArea = StagingArea.getInstance();
  }

  /**
   * Returns true if the key is in the in-memory key index.
   *
   * Not guaranteed to be correct. The cache may yet have this key even if this returns false.
   * But if it returns true, it definitely has it.
   *
   * Avoids a disk read.
   */
  public boolean containsSync(CacheKey key) {
      return mStagingArea.containsKey(key) || mFileCache.hasKeySync(key);
  }

  /**
   * Performs a key-value look up in the disk cache. If no value is found in the staging area,
   * then disk cache checks are scheduled on a background thread. Any error manifests itself as a
   * cache miss, i.e. the returned Task resolves to false.
   * @param key
   * @return Task that resolves to true if an element is found, or false otherwise
   */
  public Task<Boolean> contains(final CacheKey key) {
    if (containsSync(key)) {
      return Task.forResult(true);
    }
    return containsAsync(key);
  }

  private Task<Boolean> containsAsync(final CacheKey key) {
    try {
      return Task.call(
          new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
              return checkInStagingAreaAndFileCache(key);
            }
          },
          mReadExecutor);
    } catch (Exception exception) {
      // Log failure
      // TODO: 3697790
      FLog.w(
          TAG,
          exception,
          "Failed to schedule disk-cache read for %s",
          key.getUriString());
      return Task.forError(exception);
    }
  }

  /**
   * Performs disk cache check synchronously.
   * @param key
   * @return true if the key is found in disk cache else false
   */
  public boolean diskCheckSync(final CacheKey key) {
    if (containsSync(key)) {
      return true;
    }
    return checkInStagingAreaAndFileCache(key);
  }

  /**
   * Performs key-value look up in disk cache. If value is not found in disk cache staging area
   * then disk cache read is scheduled on background thread. Any error manifests itself as
   * cache miss, i.e. the returned task resolves to null.
   * @param key
   * @return Task that resolves to cached element or null if one cannot be retrieved;
   *   returned task never rethrows any exception
   */
  public Task<EncodedImage> get(CacheKey key, AtomicBoolean isCancelled) {
    try {
      if (FrescoSystrace.isTracing()) {
        FrescoSystrace.beginSection("BufferedDiskCache#get");
      }
      final EncodedImage pinnedImage = mStagingArea.get(key);
      if (pinnedImage != null) {
        return foundPinnedImage(key, pinnedImage);
      }
      return getAsync(key, isCancelled);
    } finally {
      if (FrescoSystrace.isTracing()) {
        FrescoSystrace.endSection();
      }
    }
  }

  /**
   * Performs key-value loop up in staging area and file cache.
   * Any error manifests itself as a miss, i.e. returns false.
   * @param key
   * @return true if the image is found in staging area or File cache, false if not found
   */
  private boolean checkInStagingAreaAndFileCache(final CacheKey key) {
    EncodedImage result = mStagingArea.get(key);
    if (result != null) {
      result.close();
      FLog.v(TAG, "Found image for %s in staging area", key.getUriString());
      mImageCacheStatsTracker.onStagingAreaHit(key);
      return true;
    } else {
      FLog.v(TAG, "Did not find image for %s in staging area", key.getUriString());
      mImageCacheStatsTracker.onStagingAreaMiss();
      try {
        return mFileCache.hasKey(key);
      } catch (Exception exception) {
        return false;
      }
    }
  }

  private Task<EncodedImage> getAsync(final CacheKey key, final AtomicBoolean isCancelled) {
    try {
      return Task.call(
          new Callable<EncodedImage>() {
            @Override
            public @Nullable EncodedImage call() throws Exception {
              try {
                if (FrescoSystrace.isTracing()) {
                  FrescoSystrace.beginSection("BufferedDiskCache#getAsync");
                }
                if (isCancelled.get()) {
                  throw new CancellationException();
                }
                EncodedImage result = mStagingArea.get(key);
                if (result != null) {
                  FLog.v(TAG, "Found image for %s in staging area", key.getUriString());
                  mImageCacheStatsTracker.onStagingAreaHit(key);
                } else {
                  FLog.v(TAG, "Did not find image for %s in staging area", key.getUriString());
                  mImageCacheStatsTracker.onStagingAreaMiss();

                  try {
                    final PooledByteBuffer buffer = readFromDiskCache(key);
                    CloseableReference<PooledByteBuffer> ref = CloseableReference.of(buffer);
                    try {
                      result = new EncodedImage(ref);
                    } finally {
                      CloseableReference.closeSafely(ref);
                    }
                  } catch (Exception exception) {
                    return null;
                  }
                }

                if (Thread.interrupted()) {
                  FLog.v(TAG, "Host thread was interrupted, decreasing reference count");
                  if (result != null) {
                    result.close();
                  }
                  throw new InterruptedException();
                } else {
                  return result;
                }
              } finally {
                if (FrescoSystrace.isTracing()) {
                  FrescoSystrace.endSection();
                }
              }
            }
          },
          mReadExecutor);<commit_msg>Check if disk cache had a miss<commit_after>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.cache;

import bolts.Task;
import com.facebook.binaryresource.BinaryResource;
import com.facebook.cache.common.CacheKey;
import com.facebook.cache.common.WriterCallback;
import com.facebook.cache.disk.FileCache;
import com.facebook.common.internal.Preconditions;
import com.facebook.common.logging.FLog;
import com.facebook.common.memory.PooledByteBuffer;
import com.facebook.common.memory.PooledByteBufferFactory;
import com.facebook.common.memory.PooledByteStreams;
import com.facebook.common.references.CloseableReference;
import com.facebook.imagepipeline.image.EncodedImage;
import com.facebook.imagepipeline.systrace.FrescoSystrace;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nullable;

/**
 * BufferedDiskCache provides get and put operations to take care of scheduling disk-cache
 * read/writes.
 */
public class BufferedDiskCache {
  private static final Class<?> TAG = BufferedDiskCache.class;

  private final FileCache mFileCache;
  private final PooledByteBufferFactory mPooledByteBufferFactory;
  private final PooledByteStreams mPooledByteStreams;
  private final Executor mReadExecutor;
  private final Executor mWriteExecutor;
  private final StagingArea mStagingArea;
  private final ImageCacheStatsTracker mImageCacheStatsTracker;

  public BufferedDiskCache(
      FileCache fileCache,
      PooledByteBufferFactory pooledByteBufferFactory,
      PooledByteStreams pooledByteStreams,
      Executor readExecutor,
      Executor writeExecutor,
      ImageCacheStatsTracker imageCacheStatsTracker) {
    mFileCache = fileCache;
    mPooledByteBufferFactory = pooledByteBufferFactory;
    mPooledByteStreams = pooledByteStreams;
    mReadExecutor = readExecutor;
    mWriteExecutor = writeExecutor;
    mImageCacheStatsTracker = imageCacheStatsTracker;
    mStagingArea = StagingArea.getInstance();
  }

  /**
   * Returns true if the key is in the in-memory key index.
   *
   * Not guaranteed to be correct. The cache may yet have this key even if this returns false.
   * But if it returns true, it definitely has it.
   *
   * Avoids a disk read.
   */
  public boolean containsSync(CacheKey key) {
      return mStagingArea.containsKey(key) || mFileCache.hasKeySync(key);
  }

  /**
   * Performs a key-value look up in the disk cache. If no value is found in the staging area,
   * then disk cache checks are scheduled on a background thread. Any error manifests itself as a
   * cache miss, i.e. the returned Task resolves to false.
   * @param key
   * @return Task that resolves to true if an element is found, or false otherwise
   */
  public Task<Boolean> contains(final CacheKey key) {
    if (containsSync(key)) {
      return Task.forResult(true);
    }
    return containsAsync(key);
  }

  private Task<Boolean> containsAsync(final CacheKey key) {
    try {
      return Task.call(
          new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
              return checkInStagingAreaAndFileCache(key);
            }
          },
          mReadExecutor);
    } catch (Exception exception) {
      // Log failure
      // TODO: 3697790
      FLog.w(
          TAG,
          exception,
          "Failed to schedule disk-cache read for %s",
          key.getUriString());
      return Task.forError(exception);
    }
  }

  /**
   * Performs disk cache check synchronously.
   * @param key
   * @return true if the key is found in disk cache else false
   */
  public boolean diskCheckSync(final CacheKey key) {
    if (containsSync(key)) {
      return true;
    }
    return checkInStagingAreaAndFileCache(key);
  }

  /**
   * Performs key-value look up in disk cache. If value is not found in disk cache staging area
   * then disk cache read is scheduled on background thread. Any error manifests itself as
   * cache miss, i.e. the returned task resolves to null.
   * @param key
   * @return Task that resolves to cached element or null if one cannot be retrieved;
   *   returned task never rethrows any exception
   */
  public Task<EncodedImage> get(CacheKey key, AtomicBoolean isCancelled) {
    try {
      if (FrescoSystrace.isTracing()) {
        FrescoSystrace.beginSection("BufferedDiskCache#get");
      }
      final EncodedImage pinnedImage = mStagingArea.get(key);
      if (pinnedImage != null) {
        return foundPinnedImage(key, pinnedImage);
      }
      return getAsync(key, isCancelled);
    } finally {
      if (FrescoSystrace.isTracing()) {
        FrescoSystrace.endSection();
      }
    }
  }

  /**
   * Performs key-value loop up in staging area and file cache.
   * Any error manifests itself as a miss, i.e. returns false.
   * @param key
   * @return true if the image is found in staging area or File cache, false if not found
   */
  private boolean checkInStagingAreaAndFileCache(final CacheKey key) {
    EncodedImage result = mStagingArea.get(key);
    if (result != null) {
      result.close();
      FLog.v(TAG, "Found image for %s in staging area", key.getUriString());
      mImageCacheStatsTracker.onStagingAreaHit(key);
      return true;
    } else {
      FLog.v(TAG, "Did not find image for %s in staging area", key.getUriString());
      mImageCacheStatsTracker.onStagingAreaMiss();
      try {
        return mFileCache.hasKey(key);
      } catch (Exception exception) {
        return false;
      }
    }
  }

  private Task<EncodedImage> getAsync(final CacheKey key, final AtomicBoolean isCancelled) {
    try {
      return Task.call(
          new Callable<EncodedImage>() {
            @Override
            public @Nullable EncodedImage call() throws Exception {
              try {
                if (FrescoSystrace.isTracing()) {
                  FrescoSystrace.beginSection("BufferedDiskCache#getAsync");
                }
                if (isCancelled.get()) {
                  throw new CancellationException();
                }
                EncodedImage result = mStagingArea.get(key);
                if (result != null) {
                  FLog.v(TAG, "Found image for %s in staging area", key.getUriString());
                  mImageCacheStatsTracker.onStagingAreaHit(key);
                } else {
                  FLog.v(TAG, "Did not find image for %s in staging area", key.getUriString());
                  mImageCacheStatsTracker.onStagingAreaMiss();

                  try {
                    final PooledByteBuffer buffer = readFromDiskCache(key);
                    if (buffer == null) {
                      return null;
                    }
                    CloseableReference<PooledByteBuffer> ref = CloseableReference.of(buffer);
                    try {
                      result = new EncodedImage(ref);
                    } finally {
                      CloseableReference.closeSafely(ref);
                    }
                  } catch (Exception exception) {
                    return null;
                  }
                }

                if (Thread.interrupted()) {
                  FLog.v(TAG, "Host thread was interrupted, decreasing reference count");
                  if (result != null) {
                    result.close();
                  }
                  throw new InterruptedException();
                } else {
                  return result;
                }
              } finally {
                if (FrescoSystrace.isTracing()) {
                  FrescoSystrace.endSection();
                }
              }
            }
          },
          mReadExecutor);
{'fraction_non_alphanumeric': 0.08078712435558909, 'fraction_numerical': 0.0008801710046523325, 'mean_word_length': 2.39675421738202, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2501243', 'n_tokens_mistral': 4389, 'n_tokens_neox': 4215, 'n_words': 1240}