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

#include "third_party/blink/renderer/modules/filesystem/file_system_dispatcher.h"

#include <utility>

#include "base/task/sequenced_task_runner.h"
#include "build/build_config.h"
#include "components/services/filesystem/public/mojom/types.mojom-blink.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "third_party/blink/public/platform/browser_interface_broker_proxy.h"
#include "third_party/blink/public/platform/file_path_conversion.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/fileapi/blob.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"

namespace blink {

class FileSystemDispatcher::WriteListener
    : public mojom::blink::FileSystemOperationListener {
 public:
  WriteListener(const WriteCallback& success_callback,
                StatusCallback error_callback)
      : error_callback_(std::move(error_callback)),
        write_callback_(success_callback) {}

  void ResultsRetrieved(
      Vector<filesystem::mojom::blink::DirectoryEntryPtr> entries,
      bool has_more) override {
    NOTREACHED();
  }

  void ErrorOccurred(base::File::Error error_code) override {
    std::move(error_callback_).Run(error_code);
  }

  void DidWrite(int64_t byte_count, bool complete) override {
    write_callback_.Run(byte_count, complete);
  }

 private:
  StatusCallback error_callback_;
  WriteCallback write_callback_;
};

class FileSystemDispatcher::ReadDirectoryListener
    : public mojom::blink::FileSystemOperationListener {
 public:
  explicit ReadDirectoryListener(std::unique_ptr<EntriesCallbacks> callbacks)
      : callbacks_(std::move(callbacks)) {}

  void ResultsRetrieved(
      Vector<filesystem::mojom::blink::DirectoryEntryPtr> entries,
      bool has_more) override {
    for (const auto& entry : entries) {
      callbacks_->DidReadDirectoryEntry(
          FilePathToString(entry->name.path()),
          entry->type == filesystem::mojom::blink::FsFileType::DIRECTORY);
    }
    callbacks_->DidReadDirectoryEntries(has_more);
  }

  void ErrorOccurred(base::File::Error error_code) override {
    callbacks_->DidFail(error_code);
  }

  void DidWrite(int64_t byte_count, bool complete) override { NOTREACHED(); }

 private:
  std::unique_ptr<EntriesCallbacks> callbacks_;
};

FileSystemDispatcher::FileSystemDispatcher(ExecutionContext& context)
    : Supplement<ExecutionContext>(context),
      file_system_manager_(&context),
      next_operation_id_(1),
      op_listeners_(&context) {}

// static
const char FileSystemDispatcher::kSupplementName[] = "FileSystemDispatcher";

// static
FileSystemDispatcher& FileSystemDispatcher::From(ExecutionContext* context) {
  DCHECK(context);
  FileSystemDispatcher* dispatcher =
      Supplement<ExecutionContext>::From<FileSystemDispatcher>(context);
  if (!dispatcher) {
    dispatcher = MakeGarbageCollected<FileSystemDispatcher>(*context);
    Supplement<ExecutionContext>::ProvideTo(*context, dispatcher);
  }
  return *dispatcher;
}

FileSystemDispatcher::~FileSystemDispatcher() = default;

mojom::blink::FileSystemManager& FileSystemDispatcher::GetFileSystemManager() {
  if (!file_system_manager_.is_bound()) {
    // See https://bit.ly/2S0zRAS for task types
    mojo::PendingReceiver<mojom::blink::FileSystemManager> receiver =
        file_system_manager_.BindNewPipeAndPassReceiver(
            GetSupplementable()->GetTaskRunner(
                blink::TaskType::kMiscPlatformAPI));

    GetSupplementable()->GetBrowserInterfaceBroker().GetInterface(
        std::move(receiver));
  }
  DCHECK(file_system_manager_.is_bound());
  return *file_system_manager_.get();
}

void FileSystemDispatcher::OpenFileSystem(
    mojom::blink::FileSystemType type,
    std::unique_ptr<FileSystemCallbacks> callbacks) {
  GetFileSystemManager().Open(
      type, blink::BindOnce(&FileSystemDispatcher::DidOpenFileSystem,
                            WrapWeakPersistent(this), std::move(callbacks)));
}

void FileSystemDispatcher::OpenFileSystemSync(
    mojom::blink::FileSystemType type,
    std::unique_ptr<FileSystemCallbacks> callbacks) {
  String name;
  KURL root_url;
  base::File::Error error_code = base::File::FILE_ERROR_FAILED;
  GetFileSystemManager().Open(type, &name, &root_url, &error_code);
  DidOpenFileSystem(std::move(callbacks), std::move(name), root_url,
                    error_code);
}

void FileSystemDispatcher::ResolveURL(
    const KURL& filesystem_url,
    std::unique_ptr<ResolveURICallbacks> callbacks) {
  GetFileSystemManager().ResolveURL(
      filesystem_url,
      blink::BindOnce(&FileSystemDispatcher::DidResolveURL,
                      WrapWeakPersistent(this), std::move(callbacks)));
}

void FileSystemDispatcher::ResolveURLSync(
    const KURL& filesystem_url,
    std::unique_ptr<ResolveURICallbacks> callbacks) {
  mojom::blink::FileSystemInfoPtr info;
  base::FilePath file_path;
  bool is_directory;
  base::File::Error error_code = base::File::FILE_ERROR_FAILED;
  GetFileSystemManager().ResolveURL(filesystem_url, &info, &file_path,
                                    &is_directory, &error_code);
  DidResolveURL(std::move(callbacks), std::move(info), std::move(file_path),
                is_directory, error_code);
}

void FileSystemDispatcher::Move(const KURL& src_path,
                                const KURL& dest_path,
                                std::unique_ptr<EntryCallbacks> callbacks) {
  GetFileSystemManager().Move(
      src_path, dest_path,
      blink::BindOnce(&FileSystemDispatcher::DidFinish,
                      WrapWeakPersistent(this), std::move(callbacks)));
}

void FileSystemDispatcher::MoveSync(const KURL& src_path,
                                    const KURL& dest_path,
                                    std::unique_ptr<EntryCallbacks> callbacks) {
  base::File::Error error_code = base::File::FILE_ERROR_FAILED;
  GetFileSystemManager().Move(src_path, dest_path, &error_code);
  DidFinish(std::move(callbacks), error_code);
}

void FileSystemDispatcher::Copy(const KURL& src_path,
                                const KURL& dest_path,
                                std::unique_ptr<EntryCallbacks> callbacks) {
  GetFileSystemManager().Copy(
      src_path, dest_path,
      blink::BindOnce(&FileSystemDispatcher::DidFinish,
                      WrapWeakPersistent(this), std::move(callbacks)));
}

void FileSystemDispatcher::CopySync(const KURL& src_path,
                                    const KURL& dest_path,
                                    std::unique_ptr<EntryCallbacks> callbacks) {
  base::File::Error error_code = base::File::FILE_ERROR_FAILED;
  GetFileSystemManager().Copy(src_path, dest_path, &error_code);
  DidFinish(std::move(callbacks), error_code);
}

void FileSystemDispatcher::Remove(const KURL& path,
                                  bool recursive,
                                  std::unique_ptr<VoidCallbacks> callbacks) {
  GetFileSystemManager().Remove(
      path, recursive,
      blink::BindOnce(&FileSystemDispatcher::DidRemove,
                      WrapWeakPersistent(this), std::move(callbacks)));
}

void FileSystemDispatcher::RemoveSync(
    const KURL& path,
    bool recursive,
    std::unique_ptr<VoidCallbacks> callbacks) {
  base::File::Error error_code = base::File::FILE_ERROR_FAILED;
  GetFileSystemManager().Remove(path, recursive, &error_code);
  DidRemove(std::move(callbacks), error_code);
}

void FileSystemDispatcher::ReadMetadata(
    const KURL& path,
    std::unique_ptr<MetadataCallbacks> callbacks) {
  GetFileSystemManager().ReadMetadata(
      path, blink::BindOnce(&FileSystemDispatcher::DidReadMetadata,
                            WrapWeakPersistent(this), std::move(callbacks)));
}

void FileSystemDispatcher::ReadMetadataSync(
    const KURL& path,
    std::unique_ptr<MetadataCallbacks> callbacks) {
  base::File::Info file_info;
  base::File::Error error_code = base::File::FILE_ERROR_FAILED;
  GetFileSystemManager().ReadMetadata(path, &file_info, &error_code);
  DidReadMetadata(std::move(callbacks), std::move(file_info), error_code);
}

void FileSystemDispatcher::CreateFile(
    const KURL& path,
    bool exclusive,
    std::unique_ptr<EntryCallbacks> callbacks) {
  GetFileSystemManager().Create(
      path, exclusive, /*is_directory=*/false, /*is_recursive=*/false,
      blink::BindOnce(&FileSystemDispatcher::DidFinish,
                      WrapWeakPersistent(this), std::move(callbacks)));
}

void FileSystemDispatcher::CreateFileSync(
    const KURL& path,
    bool exclusive,
    std::unique_ptr<EntryCallbacks> callbacks) {
  base::File::Error error_code = base::File::FILE_ERROR_FAILED;
  GetFileSystemManager().Create(path, exclusive, /*is_directory=*/false,
                                /*is_recursive=*/false, &error_code);
  DidFinish(std::move(callbacks), error_code);
}

void FileSystemDispatcher::CreateDirectory(
    const KURL& path,
    bool exclusive,
    bool recursive,
    std::unique_ptr<EntryCallbacks> callbacks) {
  GetFileSystemManager().Create(
      path, exclusive, /*is_directory=*/true, recursive,
      blink::BindOnce(&FileSystemDispatcher::DidFinish,
                      WrapWeakPersistent(this), std::move(callbacks)));
}

void FileSystemDispatcher::CreateDirectorySync(
    const KURL& path,
    bool exclusive,
    bool recursive,
    std::unique_ptr<EntryCallbacks> callbacks) {
  base::File::Error error_code = base::File::FILE_ERROR_FAILED;
  GetFileSystemManager().Create(path, exclusive, /*is_directory=*/true,
                                recursive, &error_code);
  DidFinish(std::move(callbacks), error_code);
}

void FileSystemDispatcher::Exists(const KURL& path,
                                  bool is_directory,
                                  std::unique_ptr<EntryCallbacks> callbacks) {
  GetFileSystemManager().Exists(
      path, is_directory,
      blink::BindOnce(&FileSystemDispatcher::DidFinish,
                      WrapWeakPersistent(this), std::move(callbacks)));
}

void FileSystemDispatcher::ExistsSync(
    const KURL& path,
    bool is_directory,
    std::unique_ptr<EntryCallbacks> callbacks) {
  base::File::Error error_code = base::File::FILE_ERROR_FAILED;
  GetFileSystemManager().Exists(path, is_directory, &error_code);
  DidFinish(std::move(callbacks), error_code);
}

void FileSystemDispatcher::ReadDirectory(
    const KURL& path,
    std::unique_ptr<EntriesCallbacks> callbacks) {
  mojo::PendingRemote<mojom::blink::FileSystemOperationListener> listener;
  mojo::PendingReceiver<mojom::blink::FileSystemOperationListener> receiver =
      listener.InitWithNewPipeAndPassReceiver();
  op_listeners_.Add(
      std::make_unique<ReadDirectoryListener>(std::move(callbacks)),
      std::move(receiver),
      // See https://bit.ly/2S0zRAS for task types
      GetSupplementable()->GetTaskRunner(blink::TaskType::kMiscPlatformAPI));
  GetFileSystemManager().ReadDirectory(path, std::move(listener));
}

void FileSystemDispatcher::ReadDirectorySync(
    const KURL& path,
    std::unique_ptr<EntriesCallbacks> callbacks) {
  Vector<filesystem::mojom::blink::DirectoryEntryPtr> entries;
  base::File::Error result = base::File::FILE_ERROR_FAILED;
  GetFileSystemManager().ReadDirectorySync(path, &entries, &result);
  if (result == base::File::FILE_OK) {
    DidReadDirectory(std::move(callbacks), std::move(entries),
                     std::move(result));
  }
}

void FileSystemDispatcher::InitializeFileWriter(
    const KURL& path,
    std::unique_ptr<FileWriterCallbacks> callbacks) {
  GetFileSystemManager().ReadMetadata(
      path,
      blink::BindOnce(&FileSystemDispatcher::InitializeFileWriterCallback,
                      WrapWeakPersistent(this), path, std::move(callbacks)));
}

void FileSystemDispatcher::InitializeFileWriterSync(
    const KURL& path,
    std::unique_ptr<FileWriterCallbacks> callbacks) {
  base::File::Info file_info;
  base::File::Error error_code = base::File::FILE_ERROR_FAILED;
  GetFileSystemManager().ReadMetadata(path, &file_info, &error_code);
  InitializeFileWriterCallback(path, std::move(callbacks), file_info,
                               error_code);
}

void FileSystemDispatcher::Truncate(const KURL& path,
                                    int64_t offset,
                                    int* request_id_out,
                                    StatusCallback callback) {
  HeapMojoRemote<mojom::blink::FileSystemCancellableOperation> op_remote(
      GetSupplementable());
  // See https://bit.ly/2S0zRAS for task types
  mojo::PendingReceiver<mojom::blink::FileSystemCancellableOperation>
      op_receiver = op_remote.BindNewPipeAndPassReceiver(
          GetSupplementable()->GetTaskRunner(
              blink::TaskType::kMiscPlatformAPI));
  int operation_id = next_operation_id_++;
  op_remote.set_disconnect_handler(
      BindOnce(&FileSystemDispatcher::RemoveOperationRemote,
               WrapWeakPersistent(this), operation_id));
  cancellable_operations_.insert(operation_id,
                                 WrapDisallowNew(std::move(op_remote)));
  GetFileSystemManager().Truncate(
      path, offset, std::move(op_receiver),
      blink::BindOnce(&FileSystemDispatcher::DidTruncate,
                      WrapWeakPersistent(this), operation_id,
                      std::move(callback)));

  if (request_id_out)
    *request_id_out = operation_id;
}

void FileSystemDispatcher::TruncateSync(const KURL& path,
                                        int64_t offset,
                                        StatusCallback callback) {
  base::File::Error error_code = base::File::FILE_ERROR_FAILED;
  GetFileSystemManager().TruncateSync(path, offset, &error_code);
  std::move(callback).Run(error_code);
}

void FileSystemDispatcher::Write(const KURL& path,
                                 const Blob& blob,
                                 int64_t offset,
                                 int* request_id_out,
                                 const WriteCallback& success_callback,
                                 StatusCallback error_callback) {
  HeapMojoRemote<mojom::blink::FileSystemCancellableOperation> op_remote(
      GetSupplementable());
  // See https://bit.ly/2S0zRAS for task types
  scoped_refptr<base::SequencedTaskRunner> task_runner =
      GetSupplementable()->GetTaskRunner(blink::TaskType::kMiscPlatformAPI);
  mojo::PendingReceiver<mojom::blink::FileSystemCancellableOperation>
      op_receiver = op_remote.BindNewPipeAndPassReceiver(task_runner);
  int operation_id = next_operation_id_++;
  op_remote.set_disconnect_handler(
      BindOnce(&FileSystemDispatcher::RemoveOperationRemote,
               WrapWeakPersistent(this), operation_id));
  cancellable_operations_.insert(operation_id,
                                 WrapDisallowNew(std::move(op_remote)));

  mojo::PendingRemote<mojom::blink::FileSystemOperationListener> listener;
  mojo::PendingReceiver<mojom::blink::FileSystemOperationListener> receiver =
      listener.InitWithNewPipeAndPassReceiver();
  op_listeners_.Add(
      std::make_unique<WriteListener>(
          blink::BindRepeating(&FileSystemDispatcher::DidWrite,
                               WrapWeakPersistent(this), success_callback,
                               operation_id),
          blink::BindOnce(&FileSystemDispatcher::WriteErrorCallback,
                          WrapWeakPersistent(this), std::move(error_callback),
                          operation_id)),
      std::move(receiver), task_runner);

  GetFileSystemManager().Write(path, blob.AsMojoBlob(), offset,
                               std::move(op_receiver), std::move(listener));

  if (request_id_out)
    *request_id_out = operation_id;
}

void FileSystemDispatcher::WriteSync(const KURL& path,
                                     const Blob& blob,
                                     int64_t offset,
                                     const WriteCallback& success_callback,
                                     StatusCallback error_callback) {
  int64_t byte_count;
  base::File::Error error_code = base::File::FILE_ERROR_FAILED;
  GetFileSystemManager().WriteSync(path, blob.AsMojoBlob(), offset, &byte_count,
                                   &error_code);
  if (error_code == base::File::FILE_OK)
    std::move(success_callback).Run(byte_count, /*complete=*/true);
  else
    std::move(error_callback).Run(error_code);
}

void FileSystemDispatcher::Cancel(int request_id_to_cancel,
                                  StatusCallback callback) {
  if (!cancellable_operations_.Contains(request_id_to_cancel)) {
    std::move(callback).Run(base::File::FILE_ERROR_INVALID_OPERATION);
    return;
  }
  auto& remote =
      cancellable_operations_.find(request_id_to_cancel)->value->Value();
  if (!remote.is_bound()) {
    RemoveOperationRemote(request_id_to_cancel);
    std::move(callback).Run(base::File::FILE_ERROR_INVALID_OPERATION);
    return;
  }
  remote->Cancel(blink::BindOnce(&FileSystemDispatcher::DidCancel,
                                 WrapWeakPersistent(this), std::move(callback),
                                 request_id_to_cancel));
}

void FileSystemDispatcher::CreateSnapshotFile(
    const KURL& file_path,
    std::unique_ptr<SnapshotFileCallbackBase> callbacks) {
  GetFileSystemManager().CreateSnapshotFile(
      file_path,
      blink::BindOnce(&FileSystemDispatcher::DidCreateSnapshotFile,
                      WrapWeakPersistent(this), std::move(callbacks)));
}

void FileSystemDispatcher::CreateSnapshotFileSync(
    const KURL& file_path,
    std::unique_ptr<SnapshotFileCallbackBase> callbacks) {
  base::File::Info file_info;
  base::FilePath platform_path;
  base::File::Error error_code = base::File::FILE_ERROR_FAILED;
  mojo::PendingRemote<mojom::blink::ReceivedSnapshotListener> listener;
  GetFileSystemManager().CreateSnapshotFile(
      file_path, &file_info, &platform_path, &error_code, &listener);
  DidCreateSnapshotFile(std::move(callbacks), std::move(file_info),
                        std::move(platform_path), error_code,
                        std::move(listener));
}

void FileSystemDispatcher::Trace(Visitor* visitor) const {
  visitor->Trace(file_system_manager_);
  visitor->Trace(cancellable_operations_);
  visitor->Trace(op_listeners_);
  Supplement<ExecutionContext>::Trace(visitor);
}

void FileSystemDispatcher::DidOpenFileSystem(
    std::unique_ptr<FileSystemCallbacks> callbacks,
    const String& name,
    const KURL& root,
    base::File::Error error_code) {
  if (error_code == base::File::Error::FILE_OK) {
    callbacks->DidOpenFileSystem(name, root);
  } else {
    callbacks->DidFail(error_code);
  }
}

void FileSystemDispatcher::DidResolveURL(
    std::unique_ptr<ResolveURICallbacks> callbacks,
    mojom::blink::FileSystemInfoPtr info,
    const base::FilePath& file_path,
    bool is_directory,
    base::File::Error error_code) {
  if (error_code == base::File::Error::FILE_OK) {
    DCHECK(info->root_url.IsValid());
    callbacks->DidResolveURL(info->name, info->root_url, info->mount_type,
                             FilePathToString(file_path), is_directory);
  } else {
    callbacks->DidFail(error_code);
  }
}

void FileSystemDispatcher::DidRemove(std::unique_ptr<VoidCallbacks> callbacks,
                                     base::File::Error error_code) {
  if (error_code == base::File::Error::FILE_OK)
    callbacks->DidSucceed();
  else
    callbacks->DidFail(error_code);
}

void FileSystemDispatcher::DidFinish(std::unique_ptr<EntryCallbacks> callbacks,
                                     base::File::Error error_code) {
  if (error_code == base::File::Error::FILE_OK)
    callbacks->DidSucceed();
  else
    callbacks->DidFail(error_code);
}

void FileSystemDispatcher::DidReadMetadata(
    std::unique_ptr<MetadataCallbacks> callbacks,
    const base::File::Info& file_info,
    base::File::Error error_code) {
  if (error_code == base::File::Error::FILE_OK) {
    callbacks->DidReadMetadata(FileMetadata::From(file_info));
  } else {
    callbacks->DidFail(error_code);
  }
}

void FileSystemDispatcher::DidReadDirectory(
    std::unique_ptr<EntriesCallbacks> callbacks,
    Vector<filesystem::mojom::blink::DirectoryEntryPtr> entries,
    base::File::Error error_code) {
  if (error_code == base::File::Error::FILE_OK) {
    for (const auto& entry : entries) {
      callbacks->DidReadDirectoryEntry(
          FilePathToString(entry->name.path()),
          entry->type == filesystem::mojom::blink::FsFileType::DIRECTORY);
    }
    callbacks->DidReadDirectoryEntries(false);
  } else {
    callbacks->DidFail(error_code);
  }
}

void FileSystemDispatcher::InitializeFileWriterCallback(
    const KURL& path,
    std::unique_ptr<FileWriterCallbacks> callbacks,
    const base::File::Info& file_info,
    base::File::Error error_code) {
  if (error_code == base::File::Error::FILE_OK) {
    if (file_info.is_directory || file_info.size < 0) {
      callbacks->DidFail(base::File::FILE_ERROR_FAILED);
      return;
    }
    callbacks->DidCreateFileWriter(path, file_info.size);
  } else {
    callbacks->DidFail(error_code);
  }
}

void FileSystemDispatcher::DidTruncate(int operation_id,
                                       StatusCallback callback,
                                       base::File::Error error_code) {
  if (error_code != base::File::FILE_ERROR_ABORT)
    RemoveOperationRemote(operation_id);
  std::move(callback).Run(error_code);
}

void FileSystemDispatcher::DidWrite(const WriteCallback& callback,
                                    int operation_id,
                                    int64_t bytes,
                                    bool complete) {
  callback.Run(bytes, complete);
  if (complete)
    RemoveOperationRemote(operation_id);
}

void FileSystemDispatcher::WriteErrorCallback(StatusCallback callback,
                                              int operation_id,
                                              base::File::Error error) {
  std::move(callback).Run(error);
  if (error != base::File::FILE_ERROR_ABORT)
    RemoveOperationRemote(operation_id);
}

void FileSystemDispatcher::DidCancel(StatusCallback callback,
                                     int cancelled_operation_id,
                                     base::File::Error error_code) {
  if (error_code == base::File::FILE_OK)
    RemoveOperationRemote(cancelled_operation_id);
  std::move(callback).Run(error_code);
}

void FileSystemDispatcher::DidCreateSnapshotFile(
    std::unique_ptr<SnapshotFileCallbackBase> callbacks,
    const base::File::Info& file_info,
    const base::FilePath& platform_path,
    base::File::Error error_code,
    mojo::PendingRemote<mojom::blink::ReceivedSnapshotListener> listener) {
  if (error_code == base::File::FILE_OK) {
    FileMetadata file_metadata = FileMetadata::From(file_info);
    file_metadata.platform_path = FilePathToString(platform_path);

    callbacks->DidCreateSnapshotFile(file_metadata);

    if (listener) {
      mojo::Remote<mojom::blink::ReceivedSnapshotListener>(std::move(listener))
          ->DidReceiveSnapshotFile();
    }
  } else {
    callbacks->DidFail(error_code);
  }
}

void FileSystemDispatcher::RemoveOperationRemote(int operation_id) {
  auto it = cancellable_operations_.find(operation_id);
  if (it == cancellable_operations_.end())
    return;
  cancellable_operations_.erase(it);
}

}  // namespace blink
