chromium/chrome/browser/ash/fileapi/diversion_backend_delegate.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_FILEAPI_DIVERSION_BACKEND_DELEGATE_H_
#define CHROME_BROWSER_ASH_FILEAPI_DIVERSION_BACKEND_DELEGATE_H_

#include <memory>

#include "base/memory/weak_ptr.h"
#include "chrome/browser/ash/fileapi/diversion_file_manager.h"
#include "chrome/browser/ash/fileapi/file_system_backend_delegate.h"
#include "storage/browser/file_system/async_file_util.h"

namespace ash {

// A FileSystemBackendDelegate decorator (and, transitively, an AsyncFileUtil
// decorator) that combines its wrappees with a DiversionFileManager. It
// interposes a backed-by-local-disk cache (which also enables efficient
// incremental-append writes) for potentially-remote file systems.
//
// A DiversionBackendDelegate's methods should only be called from the
// content::BrowserThread::IO thread. Callbacks run on the same thread.
class DiversionBackendDelegate : public FileSystemBackendDelegate,
                                 public storage::AsyncFileUtil {
 public:
  // How this wrapper treats a virtual file (identified by its FileSystemURL).
  enum class Policy {
    kDoNotDivert,

    // Operations (EnsureFileExists, GetFileInfo, etc) happen entirely in the
    // interposed local-disk cache, and do not touch the wrappees, up until the
    // virtual file is copied or moved to another place (a place that, if also
    // subject to this DiversionBackendDelegate, its Policy should be
    // kDoNotDivert) or an inactivity time out.
    //
    // For example, calling GetFileInfo will return FILE_ERROR_NOT_FOUND
    // (unless EnsureFileExists was previously called) even if the wrapped file
    // system has an existing file for that name (that FileSystemURL). It
    // "doesn't exist" because the wrappees are not even consulted.
    //
    // Similarly, calling EnsureFileExists will return created=true even if the
    // wrappees have an existing file for that FileSystemURL.
    //
    // This Policy is intended for "temporary files", like "*.crdownload" or
    // "*.crswap", where a potentially-large file is incrementally built over
    // time before being moved/renamed over the ultimate destination. These
    // temporary files don't really care about their name other than it doesn't
    // clash with other files. But in Chromium's //storage/browser/file_system
    // cross-platform abstraction, every virtual file needs a unique name. With
    // DiversionBackendDelegate, we can provide an isolated "overlay namespace"
    // for these temporary files, ignoring the underlying wrapped file system.
    //
    // Isolation reduces the number of spurious calls to the wrappees. Spurious
    // work can fail (unnecessarily), take a noticeable amount of time (for
    // cloud-backed file systems), add noise to metrics or debug logs, etc.
    kDivertIsolated,

    // Operations happen in the interposed local-disk cache, but unlike
    // kDivertIsolated, they consult with the wrappee file system.
    //
    // For example, EnsureFileExists will return created=false when the wrappee
    // has an existing file for that FileSystemURL.
    //
    // This Policy is intended for when you do want diversion to apply (e.g.
    // you want efficient incremental-append writes) but you don't have a
    // "temporary file".
    //
    // If in doubt, use kDivertMingled instead of kDivertIsolated, as its
    // behavior is closer to not having a DiversionBackendDelegate at all.
    kDivertMingled,
  };

  explicit DiversionBackendDelegate(
      std::unique_ptr<FileSystemBackendDelegate> wrappee);
  ~DiversionBackendDelegate() override;

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

  // FileSystemBackendDelegate overrides.
  storage::AsyncFileUtil* GetAsyncFileUtil(
      storage::FileSystemType type) override;
  std::unique_ptr<storage::FileStreamReader> CreateFileStreamReader(
      const storage::FileSystemURL& url,
      int64_t offset,
      int64_t max_bytes_to_read,
      const base::Time& expected_modification_time,
      storage::FileSystemContext* context) override;
  std::unique_ptr<storage::FileStreamWriter> CreateFileStreamWriter(
      const storage::FileSystemURL& url,
      int64_t offset,
      storage::FileSystemContext* context) override;
  storage::WatcherManager* GetWatcherManager(
      storage::FileSystemType type) override;

  // storage::AsyncFileUtil overrides.
  void CreateOrOpen(
      std::unique_ptr<storage::FileSystemOperationContext> context,
      const storage::FileSystemURL& url,
      uint32_t file_flags,
      CreateOrOpenCallback callback) override;
  void EnsureFileExists(
      std::unique_ptr<storage::FileSystemOperationContext> context,
      const storage::FileSystemURL& url,
      EnsureFileExistsCallback callback) override;
  void CreateDirectory(
      std::unique_ptr<storage::FileSystemOperationContext> context,
      const storage::FileSystemURL& url,
      bool exclusive,
      bool recursive,
      StatusCallback callback) override;
  void GetFileInfo(std::unique_ptr<storage::FileSystemOperationContext> context,
                   const storage::FileSystemURL& url,
                   GetMetadataFieldSet fields,
                   GetFileInfoCallback callback) override;
  void ReadDirectory(
      std::unique_ptr<storage::FileSystemOperationContext> context,
      const storage::FileSystemURL& url,
      ReadDirectoryCallback callback) override;
  void Touch(std::unique_ptr<storage::FileSystemOperationContext> context,
             const storage::FileSystemURL& url,
             const base::Time& last_access_time,
             const base::Time& last_modified_time,
             StatusCallback callback) override;
  void Truncate(std::unique_ptr<storage::FileSystemOperationContext> context,
                const storage::FileSystemURL& url,
                int64_t length,
                StatusCallback callback) override;
  void CopyFileLocal(
      std::unique_ptr<storage::FileSystemOperationContext> context,
      const storage::FileSystemURL& src_url,
      const storage::FileSystemURL& dest_url,
      CopyOrMoveOptionSet options,
      CopyFileProgressCallback progress_callback,
      StatusCallback callback) override;
  void MoveFileLocal(
      std::unique_ptr<storage::FileSystemOperationContext> context,
      const storage::FileSystemURL& src_url,
      const storage::FileSystemURL& dest_url,
      CopyOrMoveOptionSet options,
      StatusCallback callback) override;
  void CopyInForeignFile(
      std::unique_ptr<storage::FileSystemOperationContext> context,
      const base::FilePath& src_file_path,
      const storage::FileSystemURL& dest_url,
      StatusCallback callback) override;
  void DeleteFile(std::unique_ptr<storage::FileSystemOperationContext> context,
                  const storage::FileSystemURL& url,
                  StatusCallback callback) override;
  void DeleteDirectory(
      std::unique_ptr<storage::FileSystemOperationContext> context,
      const storage::FileSystemURL& url,
      StatusCallback callback) override;
  void DeleteRecursively(
      std::unique_ptr<storage::FileSystemOperationContext> context,
      const storage::FileSystemURL& url,
      StatusCallback callback) override;
  void CreateSnapshotFile(
      std::unique_ptr<storage::FileSystemOperationContext> context,
      const storage::FileSystemURL& url,
      CreateSnapshotFileCallback callback) override;

  void OverrideTmpfileDirForTesting(const base::FilePath& tmpfile_dir);
  static Policy ShouldDivertForTesting(const storage::FileSystemURL& url);
  static base::TimeDelta IdleTimeoutForTesting();

 private:
  enum class OnDiversionFinishedCallSite {
    kEnsureFileExists,
    kCopyFileLocal,
    kMoveFileLocal,
  };

  static void OnDiversionFinished(
      base::WeakPtr<DiversionBackendDelegate> weak_ptr,
      OnDiversionFinishedCallSite call_site,
      std::unique_ptr<storage::FileSystemOperationContext> context,
      const storage::FileSystemURL& dest_url,
      storage::AsyncFileUtil::StatusCallback callback,
      DiversionFileManager::StoppedReason stopped_reason,
      const storage::FileSystemURL& src_url,
      base::ScopedFD scoped_fd,
      int64_t file_size,
      base::File::Error error);

  std::unique_ptr<FileSystemBackendDelegate> wrappee_;
  scoped_refptr<DiversionFileManager> diversion_file_manager_;

  base::WeakPtrFactory<DiversionBackendDelegate> weak_ptr_factory_{this};
};

}  // namespace ash

#endif  // CHROME_BROWSER_ASH_FILEAPI_DIVERSION_BACKEND_DELEGATE_H_