/* 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/. */

#ifndef mozilla_dom_cache_QuotaClientImpl_h
#define mozilla_dom_cache_QuotaClientImpl_h

#include "CacheCipherKeyManager.h"
#include "mozilla/RefPtr.h"
#include "mozilla/dom/QMResult.h"
#include "mozilla/dom/cache/FileUtils.h"
#include "mozilla/dom/cache/QuotaClient.h"
#include "mozilla/dom/cache/Types.h"
#include "mozilla/dom/quota/ResultExtensions.h"

namespace mozilla::dom::cache {

class CacheQuotaClient final : public quota::Client {
  static CacheQuotaClient* sInstance;

 public:
  using OriginMetadata = quota::OriginMetadata;
  using PersistenceType = quota::PersistenceType;
  using UsageInfo = quota::UsageInfo;

  CacheQuotaClient();

  static CacheQuotaClient* Get();

  virtual Type GetType() override;

  virtual Result<UsageInfo, nsresult> InitOrigin(
      PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
      const AtomicBool& aCanceled) override;

  virtual nsresult InitOriginWithoutTracking(
      PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
      const AtomicBool& aCanceled) override;

  virtual Result<UsageInfo, nsresult> GetUsageForOrigin(
      PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
      const AtomicBool& aCanceled) override;

  virtual void OnOriginClearCompleted(
      const OriginMetadata& aOriginMetadata) override;

  void OnRepositoryClearCompleted(PersistenceType aPersistenceType) override;

  virtual void ReleaseIOThreadObjects() override;

  void AbortOperationsForLocks(
      const DirectoryLockIdTable& aDirectoryLockIds) override;

  virtual void AbortOperationsForProcess(
      ContentParentId aContentParentId) override;

  virtual void AbortAllOperations() override;

  virtual void StartIdleMaintenance() override;

  virtual void StopIdleMaintenance() override;

  nsresult UpgradeStorageFrom2_0To2_1(nsIFile* aDirectory) override;

  template <typename Callable>
  nsresult MaybeUpdatePaddingFileInternal(nsIFile& aBaseDir,
                                          mozIStorageConnection& aConn,
                                          const int64_t aIncreaseSize,
                                          const int64_t aDecreaseSize,
                                          Callable&& aCommitHook) {
    MOZ_ASSERT(!NS_IsMainThread());
    MOZ_DIAGNOSTIC_ASSERT(aIncreaseSize >= 0);
    MOZ_DIAGNOSTIC_ASSERT(aDecreaseSize >= 0);

    // Temporary should be removed at the end of each action. If not, it means
    // the failure happened.
    const bool temporaryPaddingFileExist =
        DirectoryPaddingFileExists(aBaseDir, DirPaddingFile::TMP_FILE);

    if (aIncreaseSize == aDecreaseSize && !temporaryPaddingFileExist) {
      // Early return here, since most cache actions won't modify padding size.
      QM_TRY(MOZ_TO_RESULT(aCommitHook()));

      return NS_OK;
    }

    // Don't delete the temporary padding file in case of an error to force the
    // next action recalculate the padding size.
    QM_TRY(MOZ_TO_RESULT(
        UpdateDirectoryPaddingFile(aBaseDir, aConn, aIncreaseSize,
                                   aDecreaseSize, temporaryPaddingFileExist)));

    // Don't delete the temporary padding file in case of an error to force the
    // next action recalculate the padding size.
    QM_TRY(MOZ_TO_RESULT(aCommitHook()));

    QM_WARNONLY_TRY(MOZ_TO_RESULT(DirectoryPaddingFinalizeWrite(aBaseDir)),
                    ([&aBaseDir](const nsresult) {
                      // Force restore file next time.
                      QM_WARNONLY_TRY(QM_TO_RESULT(DirectoryPaddingDeleteFile(
                          aBaseDir, DirPaddingFile::FILE)));

                      // Ensure that we are able to force the padding file to
                      // be restored.
                      MOZ_ASSERT(DirectoryPaddingFileExists(
                          aBaseDir, DirPaddingFile::TMP_FILE));

                      // Since both the body file and header have been stored
                      // in the file-system, just make the action be resolve
                      // and let the padding file be restored in the next
                      // action.
                    }));

    return NS_OK;
  }

  nsresult RestorePaddingFileInternal(nsIFile* aBaseDir,
                                      mozIStorageConnection* aConn);

  nsresult WipePaddingFileInternal(
      const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aBaseDir);

  RefPtr<CipherKeyManager> GetOrCreateCipherKeyManager(
      const quota::PrincipalMetadata& aMetadata);

 private:
  ~CacheQuotaClient();

  void InitiateShutdown() override;
  bool IsShutdownCompleted() const override;
  nsCString GetShutdownStatus() const override;
  void ForceKillActors() override;
  void FinalizeShutdown() override;

  // Should always be accessed from QM IO thread.
  nsTHashMap<nsCStringHashKey, RefPtr<CipherKeyManager>> mCipherKeyManagers;

  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CacheQuotaClient, override)
};

}  // namespace mozilla::dom::cache

#endif  // mozilla_dom_cache_QuotaClientImpl_h
