/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "FileSystemManagerParentFactory.h"

#include "mozilla/OriginAttributes.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/dom/FileSystemDataManager.h"
#include "mozilla/dom/FileSystemLog.h"
#include "mozilla/dom/FileSystemManagerParent.h"
#include "mozilla/dom/FileSystemTypes.h"
#include "mozilla/dom/quota/PrincipalUtils.h"
#include "mozilla/dom/quota/QuotaCommon.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/ResultExtensions.h"
#include "mozilla/ipc/Endpoint.h"
#include "mozilla/ipc/PBackgroundParent.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsString.h"

namespace mozilla::dom {
mozilla::ipc::IPCResult CreateFileSystemManagerParent(
    RefPtr<mozilla::ipc::PBackgroundParent> aBackgroundActor,
    const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
    mozilla::ipc::Endpoint<PFileSystemManagerParent>&& aParentEndpoint,
    std::function<void(const nsresult&)>&& aResolver) {
  using CreateActorPromise =
      MozPromise<RefPtr<FileSystemManagerParent>, nsresult, true>;

  QM_TRY(OkIf(StaticPrefs::dom_fs_enabled()), IPC_OK(),
         [aResolver](const auto&) { aResolver(NS_ERROR_DOM_NOT_ALLOWED_ERR); });

  QM_TRY(OkIf(aParentEndpoint.IsValid()), IPC_OK(),
         [aResolver](const auto&) { aResolver(NS_ERROR_INVALID_ARG); });

  // This blocks Null and Expanded principals
  QM_TRY(OkIf(quota::IsPrincipalInfoValid(aPrincipalInfo)), IPC_OK(),
         [aResolver](const auto&) { aResolver(NS_ERROR_DOM_SECURITY_ERR); });

  QM_TRY(quota::QuotaManager::EnsureCreated(), IPC_OK(),
         [aResolver](const auto rv) { aResolver(rv); });

  auto* const quotaManager = quota::QuotaManager::Get();
  MOZ_ASSERT(quotaManager);

  QM_TRY_UNWRAP(
      auto principalMetadata,
      quota::GetInfoFromValidatedPrincipalInfo(*quotaManager, aPrincipalInfo),
      IPC_OK(), [aResolver](const auto rv) { aResolver(rv); });

  quota::OriginMetadata originMetadata(std::move(principalMetadata),
                                       quota::PERSISTENCE_TYPE_DEFAULT);

  // Block use for now in PrivateBrowsing
  QM_TRY(OkIf(!OriginAttributes::IsPrivateBrowsing(originMetadata.mOrigin)),
         IPC_OK(),
         [aResolver](const auto&) { aResolver(NS_ERROR_DOM_NOT_ALLOWED_ERR); });

  LOG(("CreateFileSystemManagerParent, origin: %s",
       originMetadata.mOrigin.get()));

  RefPtr<mozilla::ipc::PBackgroundParent> backgroundActor =
      std::move(aBackgroundActor);

  // This creates the file system data manager, which has to be done on
  // PBackground
  fs::data::FileSystemDataManager::GetOrCreateFileSystemDataManager(
      originMetadata)
      ->Then(
          GetCurrentSerialEventTarget(), __func__,
          [origin = originMetadata.mOrigin,
           parentEndpoint = std::move(aParentEndpoint), backgroundActor,
           aResolver](const fs::Registered<fs::data::FileSystemDataManager>&
                          dataManager) mutable {
            QM_TRY_UNWRAP(fs::EntryId rootId, fs::data::GetRootHandle(origin),
                          QM_VOID,
                          ([backgroundActor, aResolver](const auto& aRv) {
                            if (!backgroundActor->CanSend()) {
                              return;
                            }

                            aResolver(ToNSResult(aRv));
                          }));

            InvokeAsync(
                dataManager->MutableIOTaskQueuePtr(), __func__,
                [dataManager = dataManager, rootId,
                 parentEndpoint = std::move(parentEndpoint)]() mutable {
                  RefPtr<FileSystemManagerParent> parent =
                      new FileSystemManagerParent(dataManager.inspect(),
                                                  rootId);

                  auto autoProxyDestroyFileSystemDataManagerHandle =
                      MakeScopeExit([&dataManager] {
                        nsCOMPtr<nsISerialEventTarget> target =
                            dataManager->MutableBackgroundTargetPtr();

                        MOZ_ALWAYS_SUCCEEDS(target->Dispatch(
                            NS_NewRunnableFunction(
                                "DestroyFileSystemDataManagerHandle",
                                [dataManager = std::move(dataManager)]() {}),
                            NS_DISPATCH_NORMAL));
                      });

                  LOG(("Binding parent endpoint"));
                  if (!parentEndpoint.Bind(parent)) {
                    return CreateActorPromise::CreateAndReject(NS_ERROR_FAILURE,
                                                               __func__);
                  }

                  return CreateActorPromise::CreateAndResolve(std::move(parent),
                                                              __func__);
                })
                ->Then(GetCurrentSerialEventTarget(), __func__,
                       [dataManager = dataManager](
                           CreateActorPromise::ResolveOrRejectValue&& aValue) {
                         if (aValue.IsReject()) {
                           return BoolPromise::CreateAndReject(
                               aValue.RejectValue(), __func__);
                         }

                         RefPtr<FileSystemManagerParent> parent =
                             std::move(aValue.ResolveValue());

                         if (!parent->IsAlive()) {
                           return BoolPromise::CreateAndReject(NS_ERROR_ABORT,
                                                               __func__);
                         }

                         dataManager->RegisterActor(WrapNotNull(parent));

                         return BoolPromise::CreateAndResolve(true, __func__);
                       })
                ->Then(dataManager->MutableIOTaskQueuePtr(), __func__,
                       [](const BoolPromise::ResolveOrRejectValue& aValue) {
                         // Hopping to the I/O task queue is needed to avoid
                         // a potential race triggered by
                         // FileSystemManagerParent::SendCloseAll called by
                         // FileSystemManagerParent::RequestAllowToClose called
                         // by FileSystemDataManager::RegisterActor when the
                         // directory lock has been invalidated in the
                         // meantime. The race would cause that the child side
                         // could sometimes use the child actor for sending
                         // messages and sometimes not. This extra hop
                         // guarantees that the created child actor will always
                         // refuse to send messages.
                         return BoolPromise::CreateAndResolveOrReject(aValue,
                                                                      __func__);
                       })
                ->Then(GetCurrentSerialEventTarget(), __func__,
                       [backgroundActor, aResolver](
                           const BoolPromise::ResolveOrRejectValue& aValue) {
                         if (!backgroundActor->CanSend()) {
                           return;
                         }

                         if (aValue.IsReject()) {
                           aResolver(aValue.RejectValue());
                         } else {
                           aResolver(NS_OK);
                         }
                       });
          },
          [backgroundActor, aResolver](nsresult aRejectValue) {
            if (!backgroundActor->CanSend()) {
              return;
            }

            aResolver(aRejectValue);
          });

  return IPC_OK();
}

}  // namespace mozilla::dom
