chromium/ash/components/arc/test/fake_file_system_instance.h

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

#ifndef ASH_COMPONENTS_ARC_TEST_FAKE_FILE_SYSTEM_INSTANCE_H_
#define ASH_COMPONENTS_ARC_TEST_FAKE_FILE_SYSTEM_INSTANCE_H_

#include <stdint.h>

#include <map>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "ash/components/arc/mojom/file_system.mojom.h"
#include "base/files/scoped_temp_dir.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "storage/browser/file_system/watcher_manager.h"
#include "url/gurl.h"

namespace arc {

// Fake implementation to operate on documents in memory.
//
// ArcFileSystemOperationRunner provides two types of methods: content URL
// based and documents provider based. According to backend type, you need
// to setup the fake with different functions.
//
// Content URL based functions are:
// - GetFileSize()
// - GetMimeType()
// - CloseFileSession()
// - OpenFileSessionToWrite()
// - OpenFileSessionToRead()
// Fake files for those functions can be set up by AddFile().
//
// Documents provider based functions are:
// - GetDocument()
// - GetChildDocuments()
// - GetRecentDocuments()
// - GetRoots()
// - GetRootSize()
// - DeleteDocument()
// - RenameDocument()
// - CreateDocument()
// - CopyDocument()
// - MoveDocument()
// Fake documents for those functions can be set up by AddDocument() and fake
// roots for those functions can be set up by AddRoot().
//
// Notes:
// - GetChildDocuments() returns child documents in the same order as they were
//   added with AddDocument().
// - GetRecentDocuments() returns recent documents in the same order as they
//   were added with AddRecentDocument().
// - OpenFileSessionToRead(), OpenFileSessionToWrite() will fail unless
//   AddFile() for the file to open is called beforehand.
// - Callbacks are never invoked synchronously.
// - All member functions must be called on the same thread.
class FakeFileSystemInstance : public mojom::FileSystemInstance {
 public:
  // Specification of a fake file available to content URL based methods.
  using GetLastChangeTimeCallback =
      base::RepeatingCallback<base::Time(const base::FilePath& path)>;

  // TODO(risan): Consider if this is really needed, and whether we should just
  // use the simpler option by using the original directory as is.
  // The path for the top level directory considered in MediaStore.
  static constexpr base::FilePath::CharType kFakeAndroidPath[] =
      FILE_PATH_LITERAL("/android/path");

  // Expected size in OpenThumbnail calls.
  static constexpr gfx::Size kDefaultThumbnailSize = gfx::Size(360, 360);

  struct File {
    enum class Seekable {
      NO,
      YES,
    };

    // Content URL of a file.
    std::string url;

    // The content of a file, which can be read by OpenFileSessionToRead().
    // When Seekable.NO is specified and OpenFileSessionToWrite() is called,
    // this |content| will be ignored and bytes written to FD from
    // OpenFileSessionToWrite() will be read by OpenFileSessionToRead().
    std::string content;

    // The MIME type of a file.
    std::string mime_type;

    // Whether this file is seekable or not.
    Seekable seekable;

    // Override of |content| length in bytes.
    std::optional<int64_t> size_override;

    // The thumbnail of a file, which can be read by OpenThumbnail().
    std::string thumbnail_content;

    File(const std::string& url,
         const std::string& content,
         const std::string& mime_type,
         Seekable seekable);
    File(const std::string& url,
         const std::string& content,
         const std::string& mime_type,
         Seekable seekable,
         int64_t size_override);
    File(const File& that);
    ~File();

    size_t size() const {
      return size_override ? *size_override : content.size();
    }
  };

  // Specification of a fake document available to documents provider based
  // methods.
  struct Document {
    // Authority.
    std::string authority;

    // ID of this document.
    std::string document_id;

    // ID of the parent document. Can be empty if this is a root.
    std::string parent_document_id;

    // File name displayed to users.
    std::string display_name;

    // MIME type.
    std::string mime_type;

    // File size in bytes. Set to -1 if size is not available.
    int64_t size;

    // Last modified time in milliseconds from the UNIX epoch.
    // TODO(crbug.com/40497368): Use base::Time once the corresponding field
    // in file_system.mojom stops using uint64.
    uint64_t last_modified;

    // Flag indicating that a document is deletable.
    bool supports_delete;

    // Flag indicating that a document can be renamed.
    bool supports_rename;

    // Flag indicating that a document is a directory that supports creation of
    // new files within it.
    bool dir_supports_create;

    // Flag indicating that a document supports openDocumentThumbnail() call.
    bool supports_thumbnail;

    Document(const std::string& authority,
             const std::string& document_id,
             const std::string& parent_document_id,
             const std::string& display_name,
             const std::string& mime_type,
             int64_t size,
             uint64_t last_modified);
    Document(const std::string& authority,
             const std::string& document_id,
             const std::string& parent_document_id,
             const std::string& display_name,
             const std::string& mime_type,
             int64_t size,
             uint64_t last_modified,
             bool supports_delete,
             bool supports_rename,
             bool dir_supports_create,
             bool supports_thumbnail);
    Document(const Document& that);
    ~Document();
  };

  // Specification of a fake root available to documents provider based methods.
  struct Root {
    // Authority.
    std::string authority;

    // ID of this root.
    std::string root_id;

    // ID of the document which is a directory that represents the top
    // directory of this root.
    std::string document_id;

    // Title of this root.
    std::string title;

    // Available bytes in this root.
    int64_t available_bytes;

    // Capacity bytes in this root.
    int64_t capacity_bytes;

    Root(const std::string& authority,
         const std::string& root_id,
         const std::string& document_id,
         const std::string& title,
         int64_t available_bytes,
         int64_t capacity_bytes);
    Root(const Root& that);
    ~Root();
  };

  FakeFileSystemInstance();

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

  ~FakeFileSystemInstance() override;

  // Returns true if Init() has been called.
  bool InitCalled();

  // Adds a file accessible by content URL based methods.
  void AddFile(const File& file);

  // Adds a document accessible by document provider based methods.
  void AddDocument(const Document& document);

  // Adds a recent document accessible by document provider based methods.
  void AddRecentDocument(const std::string& root_id, const Document& document);

  // Removes a recent document accessible by document provider based methods.
  void RemoveRecentDocument(const Document& document);

  // Adds a root accessible by document provider based methods.
  void AddRoot(const Root& root);

  // Add a file descriptor accessible by a URL ID.
  void AddOpenSession(const std::string& url_id, const int fd);

  // Fake the GetLastChangedTime implementation.
  void SetGetLastChangeTimeCallback(GetLastChangeTimeCallback ctime_callback);

  // Sets the path for the top level cros directory considered in MediaStore.
  // TODO(risan): Consider to not use this (if we go without different android
  // path approach). Another possibility to consider is probably to bind mount
  // the directory in the caller test and do android_path conversion for the
  // caller? In any case, this we don't want cros to be exposed to this instance
  // that lives under Android.
  void SetCrosDir(const base::FilePath& cros_dir);

  // Sets the initial media store content.
  void SetMediaStore(const std::map<base::FilePath, base::Time>& media_store);

  // Triggers watchers installed to a document.
  void TriggerWatchers(const std::string& authority,
                       const std::string& document_id,
                       storage::WatcherManager::ChangeType type);

  // Returns how many times GetChildDocuments() was called.
  int get_child_documents_count() const { return get_child_documents_count_; }

  // Returns true if there is a document with the given authority and
  // document_id.
  bool DocumentExists(const std::string& authority,
                      const std::string& document_id);

  // Returns true if there is a document with the given authority,
  // root's document_id, and file path from the root.
  bool DocumentExists(const std::string& authority,
                      const std::string& root_document_id,
                      const base::FilePath& path);

  // Returns true if there is a root with the given authority and root_id.
  bool RootExists(const std::string& authority, const std::string& root_id);

  // Returns a document with the given authority and document_id.
  Document GetDocument(const std::string& authority,
                       const std::string& document_id);

  // Returns a document with the given authority, root's document_id, and file
  // path from the root.
  Document GetDocument(const std::string& authority,
                       const std::string& document_id,
                       const base::FilePath& path);

  // Returns the content written to the FD returned by OpenFileSessionToWrite().
  std::string GetFileContent(const std::string& url);

  // Returns the content written to the FD returned by OpenFileSessionToWrite(),
  // up to |bytes| bytes.
  std::string GetFileContent(const std::string& url, size_t bytes);

  // For clearing the list of handled requests
  void clear_handled_requests() { handled_url_requests_.clear(); }

  // For validating the url requests in testing.
  const std::vector<mojom::OpenUrlsRequestPtr>& handledUrlRequests() const {
    return handled_url_requests_;
  }

  // Returns the content of the fake media store index.
  const std::map<base::FilePath, base::Time>& GetMediaStore() const {
    return media_store_;
  }

  // mojom::FileSystemInstance:
  void AddWatcher(const std::string& authority,
                  const std::string& document_id,
                  AddWatcherCallback callback) override;
  void GetChildDocuments(const std::string& authority,
                         const std::string& document_id,
                         GetChildDocumentsCallback callback) override;
  void GetDocument(const std::string& authority,
                   const std::string& document_id,
                   GetDocumentCallback callback) override;
  void GetFileSize(const std::string& url,
                   GetFileSizeCallback callback) override;
  void GetMimeType(const std::string& url,
                   GetMimeTypeCallback callback) override;
  void GetRecentDocuments(const std::string& authority,
                          const std::string& root_id,
                          GetRecentDocumentsCallback callback) override;
  void GetRoots(GetRootsCallback callback) override;
  void GetRootSize(const std::string& authority,
                   const std::string& root_id,
                   GetRootSizeCallback callback) override;
  void DeleteDocument(const std::string& authority,
                      const std::string& document_id,
                      DeleteDocumentCallback callback) override;
  void RenameDocument(const std::string& authority,
                      const std::string& document_id,
                      const std::string& display_name,
                      RenameDocumentCallback callback) override;
  void CreateDocument(const std::string& authority,
                      const std::string& parent_document_id,
                      const std::string& mime_type,
                      const std::string& display_name,
                      CreateDocumentCallback callback) override;
  void CopyDocument(const std::string& authority,
                    const std::string& source_document_id,
                    const std::string& target_parent_document_id,
                    CopyDocumentCallback callback) override;
  void MoveDocument(const std::string& authority,
                    const std::string& source_document_id,
                    const std::string& source_parent_document_id,
                    const std::string& target_parent_document_id,
                    MoveDocumentCallback callback) override;
  void Init(mojo::PendingRemote<mojom::FileSystemHost> host,
            InitCallback callback) override;
  void CloseFileSession(const std::string& url_id,
                        const std::string& error_message) override;
  void OpenFileSessionToWrite(const GURL& url,
                              OpenFileSessionToWriteCallback callback) override;
  void OpenFileSessionToRead(const GURL& url,
                             OpenFileSessionToReadCallback callback) override;
  void OpenThumbnail(const std::string& url,
                     const gfx::Size& size_hint,
                     OpenThumbnailCallback callback) override;
  void RemoveWatcher(int64_t watcher_id,
                     RemoveWatcherCallback callback) override;
  void RequestMediaScan(const std::vector<std::string>& paths) override;
  void RequestFileRemovalScan(
      const std::vector<std::string>& directory_paths) override;
  void ReindexDirectory(const std::string& directory_path) override;
  void DEPRECATED_OpenUrlsWithPermission(
      mojom::OpenUrlsRequestPtr request,
      DEPRECATED_OpenUrlsWithPermissionCallback callback) override;
  void OpenUrlsWithPermissionAndWindowInfo(
      mojom::OpenUrlsRequestPtr request,
      mojom::WindowInfoPtr window_info,
      OpenUrlsWithPermissionAndWindowInfoCallback callback) override;

 private:
  // A pair of an authority and a document ID which identifies the location
  // of a document in documents providers.
  using DocumentKey = std::pair<std::string, std::string>;

  // A pair of an authority and a root ID which identifies a root in
  // documents providers.
  using RootKey = std::pair<std::string, std::string>;

  // Finds a document inside a document with parent_document_id by following
  // given path components.
  std::string FindChildDocumentId(const std::string& authority,
                                  const std::string& parent_document_id,
                                  const std::vector<std::string>& components);

  // Creates a regular file with the given flags and returns its fd.
  base::ScopedFD CreateRegularFileDescriptor(const File& file, uint32_t flags);

  // Returns a FD to non-seekable file to read. |content| content will be
  // returned when reading the FD.
  base::ScopedFD CreateStreamFileDescriptorToRead(const std::string& content);

  // Returns a FD to non-seekable file to write. Bytes written to the FD can be
  // read by calling GetFileContent() with the passed |url|.
  base::ScopedFD CreateStreamFileDescriptorToWrite(const std::string& url);

  // Returns the cros file path for |android_path|.
  base::FilePath GetCrosPath(const base::FilePath& android_path) const;

  THREAD_CHECKER(thread_checker_);

  base::ScopedTempDir temp_dir_;

  mojo::Remote<mojom::FileSystemHost> host_remote_;

  // Mapping from a content URL to a file.
  std::map<std::string, File> files_;

  // Mapping from a content URL to a file path of a created regular file.
  std::map<std::string, base::FilePath> regular_file_paths_;

  // Mapping from a content URL to a read end of a pipe.
  // The corresponding write end should have been returned from
  // OpenFileSessionToWrite() on non-seekable file entry.
  std::map<std::string, base::ScopedFD> pipe_read_ends_;

  // Mapping from a document key to a document.
  std::map<DocumentKey, Document> documents_;

  // Mapping from a document key to its child documents.
  std::map<DocumentKey, std::vector<DocumentKey>> child_documents_;

  // Mapping from a root key to its recent documents.
  std::map<RootKey, std::vector<Document>> recent_documents_;

  // Mapping from a root key to a root.
  std::map<RootKey, Root> roots_;

  // Mapping from a document key to its watchers.
  std::map<DocumentKey, std::set<int64_t>> document_to_watchers_;

  // Mapping from a watcher ID to a document key.
  std::map<int64_t, DocumentKey> watcher_to_document_;

  // Mapping of open URL ID to a file descriptor.
  std::map<std::string, int> open_urls_;

  // List of all OpenUrlsRequests made to the fake_file_system_instance
  std::vector<mojom::OpenUrlsRequestPtr> handled_url_requests_;

  // List of roots added by AddRoot().
  std::vector<Root> roots_list_;

  // Fake MediaStore database index.
  std::map<base::FilePath, base::Time> media_store_;

  // Fake GetLastChangedTime function callback.
  GetLastChangeTimeCallback ctime_callback_;

  // The path for the top level cros directory considered in MediaStore.
  base::FilePath cros_dir_;

  int64_t next_watcher_id_ = 1;
  int get_child_documents_count_ = 0;
};

}  // namespace arc

#endif  // ASH_COMPONENTS_ARC_TEST_FAKE_FILE_SYSTEM_INSTANCE_H_