chromium/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl.h

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_CONTENT_CACHE_CONTENT_CACHE_IMPL_H_
#define CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_CONTENT_CACHE_CONTENT_CACHE_IMPL_H_

#include "base/callback_list.h"
#include "base/files/file_error_or.h"
#include "base/sequence_checker.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/sequence_bound.h"
#include "chrome/browser/ash/file_system_provider/content_cache/content_cache.h"
#include "chrome/browser/ash/file_system_provider/content_cache/content_lru_cache.h"
#include "chrome/browser/ash/file_system_provider/content_cache/context_database.h"
#include "chrome/browser/ash/file_system_provider/content_cache/local_fd.h"
#include "chrome/browser/ash/file_system_provider/opened_cloud_file.h"
#include "chrome/browser/ash/file_system_provider/provided_file_system_interface.h"

namespace ash::file_system_provider {

// The content cache for every mounted FSP. This will serve as the single point
// of orchestration between the LRU cache and the disk persistence layer.
class ContentCacheImpl : public ContentCache {
 public:
  ContentCacheImpl(const base::FilePath& root_dir,
                   BoundContextDatabase context_db,
                   size_t max_cache_size);

  ContentCacheImpl(const ContentCacheImpl&) = delete;
  ContentCacheImpl& operator=(const ContentCacheImpl&) = delete;

  ~ContentCacheImpl() override;

  // Creates a `ContentCache` with the concrete implementation.
  static std::unique_ptr<ContentCache> Create(const base::FilePath& root_dir,
                                              BoundContextDatabase context_db,
                                              size_t max_cache_items = 500);

  void SetMaxCacheItems(size_t max_cache_items) override;

  void ReadBytes(
      const OpenedCloudFile& file,
      scoped_refptr<net::IOBuffer> buffer,
      int64_t offset,
      int length,
      ProvidedFileSystemInterface::ReadChunkReceivedCallback callback) override;

  void WriteBytes(const OpenedCloudFile& file,
                  scoped_refptr<net::IOBuffer> buffer,
                  int64_t offset,
                  int length,
                  FileErrorCallback callback) override;

  void CloseFile(const OpenedCloudFile& file) override;

  void LoadFromDisk(base::OnceClosure callback) override;

  std::vector<base::FilePath> GetCachedFilePaths() override;

  void Notify(ProvidedFileSystemObserver::Changes& changes) override;

  void ObservedVersionTag(const base::FilePath& entry_path,
                          const std::string& version_tag) override;

  void Evict(const base::FilePath& file_path) override;

  void AddObserver(ContentCache::Observer* observer) override;
  void RemoveObserver(ContentCache::Observer* observer) override;

 private:
  void OnBytesRead(
      const base::FilePath& file_path,
      ProvidedFileSystemInterface::ReadChunkReceivedCallback callback,
      FileErrorOrBytesRead error_or_bytes_read);

  // Called when the database returns an ID that will be used as the file name
  // to write the bytes to disk.
  void OnFileIdGenerated(const OpenedCloudFile& file,
                         scoped_refptr<net::IOBuffer> buffer,
                         int64_t offset,
                         int length,
                         FileErrorCallback callback,
                         std::unique_ptr<int64_t> inserted_id,
                         bool item_add_success);

  void WriteBytesToDisk(const OpenedCloudFile& file,
                        scoped_refptr<net::IOBuffer> buffer,
                        int64_t offset,
                        int length,
                        FileErrorCallback callback);

  void OnBytesWritten(const base::FilePath& file_path,
                      int64_t offset,
                      int length,
                      FileErrorCallback callback,
                      base::File::Error result);

  // Invoked in the flow of `LoadFromDisk` once all the files have been
  // discovered in the FSP content cache mount directory. The results are keyed
  // by the id (i.e. the file name on disk) with a corresponding
  // `CacheFileContext` containing the total bytes on disk populated.
  void GotFilesFromDisk(base::OnceClosure callback,
                        std::map<int, int64_t> files_on_disk);

  // Invoked in the flow of `LoadFromDisk` once all the items from the database
  // have been retrieved.
  void GotItemsFromContextDatabase(base::OnceClosure callback,
                                   std::map<int, int64_t> files_on_disk,
                                   ContextDatabase::IdToItemMap items);

  // Invoked in the flow of `LoadFromDisk` once all the orphaned files (from
  // disk OR in the DB) have been removed. The `success` vector contains 2 bools
  // indicating the success of the db removal and disk removal (respectively).
  void OnStaleItemsPruned(base::OnceClosure callback,
                          std::vector<bool> prune_success);

  // Removes items individually from the disk and the lru_cache. Removes items
  // in bulk from the database.
  void RemoveItems(const std::vector<base::FilePath>& fsp_paths);

  // Removes items in bulk from the database.
  void RemoveItemsFromDatabase(std::vector<int64_t>& item_ids);

  void OnItemsRemovedFromDatabase(size_t number_of_items, bool success);

  // Removes an item with `path_on_disk` from the disk. Upon success, removes
  // item with `fsp_path` from the lru cache.
  void RemoveItemFromDisk(const base::FilePath& path_on_disk,
                          const base::FilePath& fsp_path);

  void OnItemRemovedFromDisk(const base::FilePath& fsp_path, bool success);

  // Evict the items with `file_paths`. The items are still accessible to
  // current FSP requests but inaccessible to new FSP requests. All items that
  // aren't being accessed by current FSP requests will be removed from the disk
  // and the database. Each remaining item will be removed once the last FSP
  // request for the item completes with `CloseFile()`.
  void EvictItems(const std::vector<base::FilePath>& file_paths);

  // The cache has maximum bounds on the number of items available. In the event
  // this boundary is exceeded, excess items should be evicted. There may
  // already be evicted items still in the cache (yet to be removed). The
  // remaining items to evict will be the least-recently used items.
  // TODO(b/330602540): Update the logic to also evict items when the maximum
  // size threshold has been reached.
  void EvictExcessItems();

  // Generates the absolute path on disk from the supplied `item_id`.
  const base::FilePath GetPathOnDiskFromId(int64_t item_id);

  SEQUENCE_CHECKER(sequence_checker_);

  const base::FilePath root_dir_;
  ContentLRUCache lru_cache_ GUARDED_BY_CONTEXT(sequence_checker_);

  scoped_refptr<base::SequencedTaskRunner> io_task_runner_;
  BoundContextDatabase context_db_;

  size_t max_cache_items_;
  // Number of evicted items that will be removed on the next removal cycle.
  size_t evicted_cache_items_ GUARDED_BY_CONTEXT(sequence_checker_) = 0;

  base::ObserverList<ContentCache::Observer> observers_;
  base::WeakPtrFactory<ContentCacheImpl> weak_ptr_factory_{this};
};

}  // namespace ash::file_system_provider

#endif  // CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_CONTENT_CACHE_CONTENT_CACHE_IMPL_H_