text: // Copyright 2014 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #ifndef CHROME_BROWSER_EXTENSIONS_UPDATER_LOCAL_EXTENSION_CACHE_H_
  #define CHROME_BROWSER_EXTENSIONS_UPDATER_LOCAL_EXTENSION_CACHE_H_
  
  #include <stddef.h>
  #include <stdint.h>
  
  #include <map>
  #include <memory>
  #include <string>
  
  #include "base/callback_forward.h"
  #include "base/files/file_path.h"
  #include "base/macros.h"
  #include "base/memory/weak_ptr.h"
  #include "base/time/time.h"
  
  namespace base {
  class SequencedTaskRunner;
  }
  
  namespace extensions {
  
  // Cache .crx files in some local dir for future use. Cache keeps only latest
  // version of the extensions. Only one instance of LocalExtensionCache can work
  // with the same directory. But LocalExtensionCache instance can be shared
  // between multiple clients. Public interface can be used only from UI thread.
  class LocalExtensionCache {
   public:
    // Callback invoked on UI thread when PutExtension is completed.
    typedef base::Callback<void(const base::FilePath& file_path,
                                bool file_ownership_passed)> PutExtensionCallback;
  
    // |cache_dir| - directory that will be used for caching CRX files.
    // |max_cache_size| - maximum disk space that cache can use, 0 means no limit.
    // |max_cache_age| - maximum age that unused item can be kept in cache, 0 age
    // means that all unused cache items will be removed on Shutdown.
    // All file I/O is done via the |backend_task_runner|.
    LocalExtensionCache(
        const base::FilePath& cache_dir,
        uint64_t max_cache_size,
        const base::TimeDelta& max_cache_age,
        const scoped_refptr<base::SequencedTaskRunner>& backend_task_runner);
    ~LocalExtensionCache();
  
    // Name of flag file that indicates that cache is ready (import finished).
    static const char kCacheReadyFlagFileName[];
  
    // Initialize cache. If |wait_for_cache_initialization| is |true|, the cache
    // contents will not be read until a flag file appears in the cache directory,
    // signaling that the cache is ready. The |callback| is called when cache is
    // ready and cache dir content was already checked.
    void Init(bool wait_for_cache_initialization,
              const base::Closure& callback);
  
    // Shut down the cache. The |callback| will be invoked when the cache has shut
    // down completely and there are no more pending file I/O operations.
    void Shutdown(const base::Closure& callback);
  
    // If extension with |id| and |expected_hash| exists in the cache (or there
    // is an extension with the same |id|, but without expected hash sum),
    // returns |true|, |file_path| and |version| for the found extension.
    // If |file_path| was requested, then extension will be marked as used with
    // current timestamp.
    bool GetExtension(const std::string& id,
                      const std::string& expected_hash,
                      base::FilePath* file_path,
                      std::string* version);
  
    // Returns |true| if there is a file with |id| and |expected_hash| in the
    // cache, and its hash sum is actually empty. After removing it from cache and
    // re-downloading, the new entry will have some non-empty hash sum.
    bool ShouldRetryDownload(const std::string& id,
                             const std::string& expected_hash);
  
    // Put extension with |id|, |version| and |expected_hash| into local cache.
    // Older version in the cache will be deleted on next run so it can be safely
    // used. Extension will be marked as used with current timestamp. The file
    // will be available via GetExtension when |callback| is called. PutExtension
    // may get ownership of |file_path| or return it back via |callback|.
    void PutExtension(const std::string& id,
                      const std::string& expected_hash,
                      const base::FilePath& file_path,
                      const std::string& version,
                      const PutExtensionCallback& callback);
  
    // Remove extension with |id| and |expected_hash| from local cache,
    // corresponding crx file will be removed from disk too. If |expected_hash| is
    // empty, all files corresponding to that |id| will be removed.
    bool RemoveExtension(const std::string& id, const std::string& expected_hash);
  
    // Return cache statistics. Returns |false| if cache is not ready.
    bool GetStatistics(uint64_t* cache_size, size_t* extensions_count);
  
    // Outputs properly formatted extension file name, as it will be stored in
    // cache. If |expected_hash| is empty, it will be <id>-<version>.crx,
    // otherwise the name format is <id>-<version>-<hash>.crx.
    static std::string ExtensionFileName(const std::string& id,
                                         const std::string& version,
                                         const std::string& expected_hash);
  
    bool is_ready() const { return state_ == kReady; }
    bool is_uninitialized() const { return state_ == kUninitialized; }
    bool is_shutdown() const { return state_ == kShutdown; }
  
    // For tests only!
    void SetCacheStatusPollingDelayForTests(const base::TimeDelta& delay);
  
   private:
    struct CacheItemInfo {
      std::string version;
      std::string expected_hash;
      base::Time last_used;
      uint64_t size;
      base::FilePath file_path;
  
      CacheItemInfo(const std::string& version,
                    const std::string& expected_hash,
                    const base::Time& last_used,
                    uint64_t size,
                    const base::FilePath& file_path);
      CacheItemInfo(const CacheItemInfo& other);
      ~CacheItemInfo();
    };
    typedef std::multimap<std::string, CacheItemInfo> CacheMap;
    typedef std::pair<CacheMap::iterator, CacheMap::iterator> CacheHit;
  
    enum State {
      kUninitialized,
      kWaitInitialization,
      kReady,
      kShutdown
    };
  
    // Helper function that searches the cache map for an extension with the
    // specified |id| and |expected_hash|. If there is an extension with empty
    // hash in the map, it will be returned. If |expected_hash| is empty, returns
    // the first extension with the same |id|.
    static CacheMap::iterator FindExtension(CacheMap& cache,
                                            const std::string& id,
                                            const std::string& expected_hash);
  
    // Helper function that compares a cache entry (typically returned from
    // FindExtension) with an incoming |version| and |expected_hash|. Comparison
    // is based on the version number (newer is better) and hash sum (it is
    // better to have a file with an expected hash sum than without it).
    // Return value of this function is |true| if we already have a 'better'
    // entry in cache (considering both version number and hash sum), and the
    // value of |compare| is set to the version number comparison result (as
    // returned by Version::CompareTo).
    static bool NewerOrSame(const CacheMap::iterator& entry,
                            const std::string& version,
                            const std::string& expected_hash,
                            int* compare);
  
    // Helper function that checks if there is already a newer version of the
    // extension we want to add to the cache, or if there is already a file with a
    // hash sum (and we are trying to add one without it), or vice versa. Keeps
    // the invariant of having only one version of each extension, and either only
    // unhashed (single) or only hashed (multiple) variants of that version.
    // |delete_files| specifies if this function is called on startup (in which
    // case we will clean up files we don't need), or on extension install.
    // Returns cache.end() if the extension is already cached, or an iterator to
    // the inserted cache entry otherwise.
    static CacheMap::iterator InsertCacheEntry(CacheMap& cache,
                                               const std::string& id,
                                               const CacheItemInfo& info,
                                               const bool delete_files);
  
    // Remove extension at a specified iterator. This is necessary because
    // removing an extension by |id| and |expected_hash| taken by reference from
    // an iterator leads to use-after-free. On the other hand, when passing the
    // iterator itself we avoid lookup as such, at all.
    // For external calls from RemoveExtension without expected hash we will
    // ignore the hash in iterator by setting |match_hash| to false.
    bool RemoveExtensionAt(const CacheMap::iterator& it, bool match_hash);
  
    // Sends BackendCheckCacheStatus task on backend thread.
    void CheckCacheStatus(const base::Closure& callback);
  
    // Checks whether a flag file exists in the |cache_dir|, indicating that the
    // cache is ready. This method is invoked via the |backend_task_runner_| and
    // posts its result back to the |local_cache| on the UI thread.
    static void BackendCheckCacheStatus(
        base::WeakPtr<LocalExtensionCache> local_cache,
        const base::FilePath& cache_dir,
        const base::Closure& callback);
  
    // Invoked on the UI thread after checking whether the cache is ready. If the
    // cache is not ready yet, posts a delayed task that will repeat the check,
    // thus polling for cache readiness.
    void OnCacheStatusChecked(bool ready, const base::Closure& callback);
  
    // Checks the cache contents. This is a helper that invokes the actual check
    // by posting to the |backend_task_runner_|.
    void CheckCacheContents(const base::Closure& callback);
  
    // Checks the cache contents. This method is invoked via the
    // |backend_task_runner_| and posts back a list of cache entries to the
    // |local_cache| on the UI thread.
    static void BackendCheckCacheContents(
        base::WeakPtr<LocalExtensionCache> local_cache,
        const base::FilePath& cache_dir,
        const base::Closure& callback);
  
    // Helper for BackendCheckCacheContents() that updates |cache_content|.
    static void BackendCheckCacheContentsInternal(
        const base::FilePath& cache_dir,
        CacheMap* cache_content);
  
    // Invoked when the cache content on disk has been checked. |cache_content|
    // contains all the currently valid crx files in the cache.
    void OnCacheContentsChecked(std::unique_ptr<CacheMap> cache_content,
                                const base::Closure& callback);
  
    // Update timestamp for the file to mark it as "used". This method is invoked
    // via the |backend_task_runner_|.
    static void BackendMarkFileUsed(const base::FilePath& file_path,
                                    const base::Time& time);
  
    // Installs the downloaded crx file at |path| in the |cache_dir|. This method
    // is invoked via the |backend_task_runner_|.
    static void BackendInstallCacheEntry(
        base::WeakPtr<LocalExtensionCache> local_cache,
        const base::FilePath& cache_dir,
        const std::string& id,
        const std::string& expected_hash,
        const base::FilePath& file_path,
        const std::string& version,
        const PutExtensionCallback& callback);
  
    // Invoked on the UI thread when a new entry has been installed in the cache.
    void OnCacheEntryInstalled(const std::string& id,
                               const CacheItemInfo& info,
                               bool was_error,
                               const PutExtensionCallback& callback);
  
    // Remove cached crx files(all versions) under |cached_dir| for extension with
    // |id|. This method is invoked via the |backend_task_runner_|.
    static void BackendRemoveCacheEntry(const base::FilePath& cache_dir,
                                        const std::string& expected_hash,
                                        const std::string& id);
  
    // Compare two cache items returns true if first item is older.
    static bool CompareCacheItemsAge(const CacheMap::iterator& lhs,
                                     const CacheMap::iterator& rhs);
  
    // Calculate which files need to be deleted and schedule files deletion.
    void CleanUp();
  
    // Path to the directory where the extension cache is stored.
    base::FilePath cache_dir_;
  
    // Maximum size of cache dir on disk.
    uint64_t max_cache_size_;
  
    // Minimal age of unused item in cache, items prior to this age will be
    // deleted on shutdown.
    base::Time min_cache_age_;
  
    // Task runner for executing file I/O tasks.
    scoped_refptr<base::SequencedTaskRunner> backend_task_runner_;
  
    // Track state of the instance.
    State state_;
  
    // This contains info about all cached extensions.
    CacheMap cached_extensions_;
  
    // Delay between polling cache status.
    base::TimeDelta cache_status_polling_delay_;
  
    // Weak factory for callbacks from the backend and delayed tasks.
    base::WeakPtrFactory<LocalExtensionCache> weak_ptr_factory_;
  
    DISALLOW_COPY_AND_ASSIGN(LocalExtensionCache);
  };
  
  }  // namespace extensions
  
  #endif  // CHROME_BROWSER_EXTENSIONS_UPDATER_LOCAL_EXTENSION_CACHE_H_
  