/* 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_quota_quotamanager_h_
#define mozilla_dom_quota_quotamanager_h_

#include <cstdint>
#include <utility>

#include "Client.h"
#include "ErrorList.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/Assertions.h"
#include "mozilla/InitializedOnce.h"
#include "mozilla/MozPromise.h"
#include "mozilla/Mutex.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Result.h"
#include "mozilla/ThreadBound.h"
#include "mozilla/dom/Nullable.h"
#include "mozilla/dom/ipc/IdType.h"
#include "mozilla/dom/quota/Assertions.h"
#include "mozilla/dom/quota/BackgroundThreadObject.h"
#include "mozilla/dom/quota/CommonMetadata.h"
#include "mozilla/dom/quota/DirectoryLockCategory.h"
#include "mozilla/dom/quota/ForwardDecls.h"
#include "mozilla/dom/quota/HashKeys.h"
#include "mozilla/dom/quota/InitializationTypes.h"
#include "mozilla/dom/quota/NotifyUtils.h"
#include "mozilla/dom/quota/OpenClientDirectoryInfo.h"
#include "mozilla/dom/quota/OriginOperationCallbacks.h"
#include "mozilla/dom/quota/PersistenceType.h"
#include "nsCOMPtr.h"
#include "nsClassHashtable.h"
#include "nsDebug.h"
#include "nsHashKeys.h"
#include "nsISupports.h"
#include "nsStringFwd.h"
#include "nsTArray.h"
#include "nsTHashMap.h"
#include "nsTStringRepr.h"
#include "nscore.h"
#include "prenv.h"

#define GTEST_CLASS(testFixture, testName) testFixture##_##testName##_Test

class mozIStorageConnection;
class nsIEventTarget;
class nsIFile;
class nsIRunnable;
class nsIThread;
class nsITimer;

namespace mozilla {

class OriginAttributes;
class OriginAttributesPattern;

namespace ipc {

class PrincipalInfo;

}  // namespace ipc

}  // namespace mozilla

namespace mozilla::dom::quota {

class CanonicalQuotaObject;
class ClearDataOp;
class ClearRequestBase;
class ClientStorageScope;
class ClientUsageArray;
class ClientDirectoryLock;
class ClientDirectoryLockHandle;
class DirectoryLockImpl;
class GroupInfo;
class GroupInfoPair;
class NormalOriginOperationBase;
class OriginDirectoryLock;
class OriginInfo;
class OriginScope;
class QuotaObject;
class SaveOriginAccessTimeOp;
class UniversalDirectoryLock;

namespace test {
class GTEST_CLASS(TestQuotaManagerAndShutdownFixture,
                  ThumbnailPrivateIdentityTemporaryOriginCount);
}

class QuotaManager final : public BackgroundThreadObject {
  friend class CanonicalQuotaObject;
  friend class ClearDataOp;
  friend class ClearRequestBase;
  friend class ClearStorageOp;
  friend class ClientDirectoryLockHandle;
  friend class DirectoryLockImpl;
  friend class FinalizeOriginEvictionOp;
  friend class GroupInfo;
  friend class InitOp;
  friend class InitializePersistentOriginOp;
  friend class InitializePersistentStorageOp;
  friend class InitializeTemporaryGroupOp;
  friend class InitializeTemporaryOriginOp;
  friend class InitTemporaryStorageOp;
  friend class ListCachedOriginsOp;
  friend class OriginInfo;
  friend class PersistOp;
  friend class SaveOriginAccessTimeOp;
  friend class ShutdownStorageOp;
  friend class test::GTEST_CLASS(TestQuotaManagerAndShutdownFixture,
                                 ThumbnailPrivateIdentityTemporaryOriginCount);
  friend class UniversalDirectoryLock;

  friend Result<PrincipalMetadata, nsresult> GetInfoFromValidatedPrincipalInfo(
      QuotaManager& aQuotaManager,
      const mozilla::ipc::PrincipalInfo& aPrincipalInfo);

  using PrincipalInfo = mozilla::ipc::PrincipalInfo;

  class Observer;

 public:
  using ClientDirectoryLockHandlePromise =
      MozPromise<ClientDirectoryLockHandle, nsresult, true>;

  QuotaManager(const nsAString& aBasePath, const nsAString& aStorageName);

  NS_INLINE_DECL_REFCOUNTING(QuotaManager)

  static nsresult Initialize();

  static bool IsRunningXPCShellTests() {
    static bool kRunningXPCShellTests =
        !!PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR");
    return kRunningXPCShellTests;
  }

  static bool IsRunningGTests() {
    static bool kRunningGTests = !!PR_GetEnv("MOZ_RUN_GTEST");
    return kRunningGTests;
  }

  static const char kReplaceChars[];
  static const char16_t kReplaceChars16[];

  static Result<MovingNotNull<RefPtr<QuotaManager>>, nsresult> GetOrCreate();

  static Result<Ok, nsresult> EnsureCreated();

  // Returns a non-owning reference.
  static QuotaManager* Get();

  // Use only in gtests!
  static nsIObserver* GetObserver();

  /**
   * Ensures that all pending normal origin operations and their follow-up
   * events are processed and completed.
   *
   * This is useful in cases where operations are scheduled asynchronously
   * without a way to explicitly await their completion, and must be finalized
   * before continuing with further checks or logic.
   *
   * This method asserts that gtests are currently running and must not be used
   * outside of gtest code.
   */
  static void ProcessPendingNormalOriginOperations();

  // Returns true if we've begun the shutdown process.
  static bool IsShuttingDown();

  static void ShutdownInstance();

  // Use only in gtests!
  static void Reset();

  static bool IsOSMetadata(const nsAString& aFileName);

  static bool IsDotFile(const nsAString& aFileName);

  void RegisterNormalOriginOp(NormalOriginOperationBase& aNormalOriginOp);

  void UnregisterNormalOriginOp(NormalOriginOperationBase& aNormalOriginOp);

  bool IsPersistentOriginInitializedInternal(const nsACString& aOrigin) const {
    AssertIsOnIOThread();

    return mInitializedOriginsInternal.Contains(aOrigin);
  }

  bool IsTemporaryStorageInitializedInternal() const {
    AssertIsOnIOThread();

    return mTemporaryStorageInitializedInternal;
  }

  /**
   * For initialization of an origin where the directory either exists or it
   * does not. The directory exists case is used by InitializeOrigin once it
   * has tallied origin usage by calling each of the QuotaClient InitOrigin
   * methods. It's also used by LoadQuota when quota information is available
   * from the cache. EnsureTemporaryStorageIsInitializedInternal calls this
   * either if the directory exists or it does not depending on requirements
   * of a particular quota client. The special case when origin directory is
   * not created during origin initialization is currently utilized only by
   * LSNG.
   */
  void InitQuotaForOrigin(const FullOriginMetadata& aFullOriginMetadata,
                          bool aDirectoryExists = true);

  // XXX clients can use QuotaObject instead of calling this method directly.
  void DecreaseUsageForClient(const ClientMetadata& aClientMetadata,
                              int64_t aSize);

  void ResetUsageForClient(const ClientMetadata& aClientMetadata);

  UsageInfo GetUsageForClient(PersistenceType aPersistenceType,
                              const OriginMetadata& aOriginMetadata,
                              Client::Type aClientType);

  void UpdateOriginAccessTime(const OriginMetadata& aOriginMetadata,
                              int64_t aTimestamp);

  void UpdateOriginMaintenanceDate(const OriginMetadata& aOriginMetadata,
                                   int32_t aMaintenanceDate);

  void UpdateOriginAccessed(const OriginMetadata& aOriginMetadata);

  void RemoveQuota();

  void RemoveQuotaForRepository(PersistenceType aPersistenceType) {
    MutexAutoLock lock(mQuotaMutex);
    LockedRemoveQuotaForRepository(aPersistenceType);
  }

  void RemoveQuotaForOrigin(PersistenceType aPersistenceType,
                            const OriginMetadata& aOriginMetadata) {
    MutexAutoLock lock(mQuotaMutex);
    LockedRemoveQuotaForOrigin(aOriginMetadata);
  }

  nsresult LoadQuota();

  void UnloadQuota();

  void RemoveOriginFromCache(const OriginMetadata& aOriginMetadata);

  already_AddRefed<QuotaObject> GetQuotaObject(
      PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
      Client::Type aClientType, nsIFile* aFile, int64_t aFileSize = -1,
      int64_t* aFileSizeOut = nullptr);

  already_AddRefed<QuotaObject> GetQuotaObject(
      PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
      Client::Type aClientType, const nsAString& aPath, int64_t aFileSize = -1,
      int64_t* aFileSizeOut = nullptr);

  already_AddRefed<QuotaObject> GetQuotaObject(const int64_t aDirectoryLockId,
                                               const nsAString& aPath);

  Nullable<bool> OriginPersisted(const OriginMetadata& aOriginMetadata);

  void PersistOrigin(const OriginMetadata& aOriginMetadata);

  template <typename F>
  auto WithOriginInfo(const OriginMetadata& aOriginMetadata, F aFunction)
      -> std::invoke_result_t<F, const RefPtr<OriginInfo>&>;

  using DirectoryLockIdTableArray =
      AutoTArray<Client::DirectoryLockIdTable, Client::TYPE_MAX>;
  void AbortOperationsForLocks(const DirectoryLockIdTableArray& aLockIds);

  // Called when a process is being shot down. Aborts any running operations
  // for the given process.
  void AbortOperationsForProcess(ContentParentId aContentParentId);

  Result<nsCOMPtr<nsIFile>, nsresult> GetOriginDirectory(
      const OriginMetadata& aOriginMetadata) const;

  Result<bool, nsresult> DoesOriginDirectoryExist(
      const OriginMetadata& aOriginMetadata) const;

  Result<nsCOMPtr<nsIFile>, nsresult> GetOrCreateTemporaryOriginDirectory(
      const OriginMetadata& aOriginMetadata);

  Result<Ok, nsresult> EnsureTemporaryOriginDirectoryCreated(
      const OriginMetadata& aOriginMetadata);

  nsresult CreateDirectoryMetadata2(
      nsIFile& aDirectory, const FullOriginMetadata& aFullOriginMetadata);

  nsresult RestoreDirectoryMetadata2(nsIFile* aDirectory);

  // XXX Remove aPersistenceType argument once the persistence type is stored
  // in the metadata file.
  Result<FullOriginMetadata, nsresult> LoadFullOriginMetadata(
      nsIFile* aDirectory, PersistenceType aPersistenceType);

  Result<FullOriginMetadata, nsresult> LoadFullOriginMetadataWithRestore(
      nsIFile* aDirectory);

  Result<std::pair<FullOriginMetadata, bool /* restore status */>, nsresult>
  LoadFullOriginMetadataWithRestoreAndStatus(nsIFile* aDirectory);

  Result<OriginMetadata, nsresult> GetOriginMetadata(nsIFile* aDirectory);

  Result<Ok, nsresult> RemoveOriginDirectory(nsIFile& aDirectory);

  Result<bool, nsresult> DoesClientDirectoryExist(
      const ClientMetadata& aClientMetadata) const;

  RefPtr<UniversalDirectoryLockPromise> OpenStorageDirectory(
      const PersistenceScope& aPersistenceScope,
      const OriginScope& aOriginScope,
      const ClientStorageScope& aClientStorageScope, bool aExclusive,
      bool aInitializeOrigins = false,
      DirectoryLockCategory aCategory = DirectoryLockCategory::None,
      Maybe<RefPtr<UniversalDirectoryLock>&> aPendingDirectoryLockOut =
          Nothing());

  // This is the main entry point into the QuotaManager API.
  // Any storage API implementation (quota client) that participates in
  // centralized quota and storage handling should call this method to obtain
  // a directory lock, ensuring the client’s files are protected from deletion
  // while in use.
  //
  // After a lock is acquired, the client is notified by resolving the returned
  // promise. If the lock couldn't be acquired, the promise is rejected.
  //
  // The returned lock is encapsulated in ClientDirectoryLockHandle, which
  // manages ownership and automatically drops the lock when destroyed. Clients
  // should retain ownership of the handle for as long as the lock is needed.
  //
  // The lock may still be invalidated by a clear operation, so consumers
  // should check its validity and release it as soon as it is no longer
  // required.
  //
  // Internally, QuotaManager may perform various initialization steps before
  // resolving the promise. This can include storage, temporary storage, group
  // and origin initialization.
  //
  // Optionally, an output parameter (aPendingDirectoryLockOut) can be provided
  // to receive a reference to the ClientDirectoryLock before wrapping it in
  // ClientDirectoryLockHandle. This allows tracking pending locks separately.
  RefPtr<ClientDirectoryLockHandlePromise> OpenClientDirectory(
      const ClientMetadata& aClientMetadata, bool aInitializeOrigins = true,
      bool aCreateIfNonExistent = true,
      Maybe<RefPtr<ClientDirectoryLock>&> aPendingDirectoryLockOut = Nothing());

  RefPtr<ClientDirectoryLockHandlePromise> OpenClientDirectoryImpl(
      const ClientMetadata& aClientMetadata, bool aInitializeOrigins,
      bool aCreateIfNonExistent,
      Maybe<RefPtr<ClientDirectoryLock>&> aPendingDirectoryLockOut);

  RefPtr<ClientDirectoryLock> CreateDirectoryLock(
      const ClientMetadata& aClientMetadata, bool aExclusive);

  // XXX RemoveMe once bug 1170279 gets fixed.
  RefPtr<UniversalDirectoryLock> CreateDirectoryLockInternal(
      const PersistenceScope& aPersistenceScope,
      const OriginScope& aOriginScope,
      const ClientStorageScope& aClientStorageScope, bool aExclusive,
      DirectoryLockCategory aCategory = DirectoryLockCategory::None);

  // Collect inactive and the least recently used origins.
  uint64_t CollectOriginsForEviction(
      uint64_t aMinSizeToBeFreed,
      nsTArray<RefPtr<OriginDirectoryLock>>& aLocks);

  /**
   * Helper method to invoke the provided predicate on all "pending" OriginInfo
   * instances. These are origins for which the origin directory has not yet
   * been created but for which quota is already being tracked. This happens,
   * for example, for the LocalStorage client where an origin that previously
   * was not using LocalStorage can start issuing writes which it buffers until
   * eventually flushing them. We defer creating the origin directory for as
   * long as possible in that case, so the directory won't exist. Logic that
   * would otherwise only consult the filesystem also needs to use this method.
   */
  template <typename P>
  void CollectPendingOriginsForListing(P aPredicate);

  bool IsPendingOrigin(const OriginMetadata& aOriginMetadata) const;

  RefPtr<BoolPromise> InitializeStorage();

  RefPtr<BoolPromise> InitializeStorage(
      RefPtr<UniversalDirectoryLock> aDirectoryLock);

  RefPtr<BoolPromise> StorageInitialized();

  bool IsStorageInitialized() const {
    AssertIsOnOwningThread();

    return mStorageInitialized;
  }

  bool IsStorageInitializedInternal() const {
    AssertIsOnIOThread();
    return static_cast<bool>(mStorageConnection);
  }

  void AssertStorageIsInitializedInternal() const
#ifdef DEBUG
      ;
#else
  {
  }
#endif

  RefPtr<BoolPromise> TemporaryStorageInitialized();

 private:
  nsresult EnsureStorageIsInitializedInternal();

 public:
  RefPtr<BoolPromise> InitializePersistentStorage();

  RefPtr<BoolPromise> InitializePersistentStorage(
      RefPtr<UniversalDirectoryLock> aDirectoryLock);

  RefPtr<BoolPromise> PersistentStorageInitialized();

  bool IsPersistentStorageInitialized() const {
    AssertIsOnOwningThread();

    return mPersistentStorageInitialized;
  }

  bool IsPersistentStorageInitializedInternal() const {
    AssertIsOnIOThread();

    return mPersistentStorageInitializedInternal;
  }

 private:
  nsresult EnsurePersistentStorageIsInitializedInternal();

 public:
  RefPtr<BoolPromise> InitializeTemporaryGroup(
      const PrincipalMetadata& aPrincipalMetadata);

  RefPtr<BoolPromise> InitializeTemporaryGroup(
      const PrincipalMetadata& aPrincipalMetadata,
      RefPtr<UniversalDirectoryLock> aDirectoryLock);

  RefPtr<BoolPromise> TemporaryGroupInitialized(
      const PrincipalMetadata& aPrincipalMetadata);

  bool IsTemporaryGroupInitialized(const PrincipalMetadata& aPrincipalMetadata);

  bool IsTemporaryGroupInitializedInternal(
      const PrincipalMetadata& aPrincipalMetadata) const;

 private:
  Result<Ok, nsresult> EnsureTemporaryGroupIsInitializedInternal(
      const PrincipalMetadata& aPrincipalMetadata);

 public:
  RefPtr<BoolPromise> InitializePersistentOrigin(
      const OriginMetadata& aOriginMetadata);

  RefPtr<BoolPromise> InitializePersistentOrigin(
      const OriginMetadata& aOriginMetadata,
      RefPtr<UniversalDirectoryLock> aDirectoryLock);

  RefPtr<BoolPromise> PersistentOriginInitialized(
      const OriginMetadata& aOriginMetadata);

  bool IsPersistentOriginInitialized(const OriginMetadata& aOriginMetadata);

  bool IsPersistentOriginInitializedInternal(
      const OriginMetadata& aOriginMetadata) const;

 private:
  // Returns a pair of an nsIFile object referring to the directory, and a bool
  // indicating whether the directory was newly created.
  Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
  EnsurePersistentOriginIsInitializedInternal(
      const OriginMetadata& aOriginMetadata);

 public:
  RefPtr<BoolPromise> InitializeTemporaryOrigin(
      const OriginMetadata& aOriginMetadata, bool aCreateIfNonExistent);

  RefPtr<BoolPromise> InitializeTemporaryOrigin(
      const OriginMetadata& aOriginMetadata, bool aCreateIfNonExistent,
      RefPtr<UniversalDirectoryLock> aDirectoryLock);

  RefPtr<BoolPromise> TemporaryOriginInitialized(
      const OriginMetadata& aOriginMetadata);

  bool IsTemporaryOriginInitialized(const OriginMetadata& aOriginMetadata);

  bool IsTemporaryOriginInitializedInternal(
      const OriginMetadata& aOriginMetadata) const;

 private:
  // Returns a pair of an nsIFile object referring to the directory, and a bool
  // indicating whether the directory was newly created.
  Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
  EnsureTemporaryOriginIsInitializedInternal(
      const OriginMetadata& aOriginMetadata, bool aCreateIfNonExistent);

 public:
  RefPtr<BoolPromise> InitializePersistentClient(
      const ClientMetadata& aClientMetadata);

  RefPtr<BoolPromise> InitializePersistentClient(
      const ClientMetadata& aClientMetadata,
      RefPtr<UniversalDirectoryLock> aDirectoryLock);

  bool IsPersistentClientInitialized(const ClientMetadata& aClientMetadata);

  // Returns a pair of an nsIFile object referring to the directory, and a bool
  // indicating whether the directory was newly created.
  Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
  EnsurePersistentClientIsInitialized(const ClientMetadata& aClientMetadata);

  RefPtr<BoolPromise> InitializeTemporaryClient(
      const ClientMetadata& aClientMetadata, bool aCreateIfNonExistent);

  RefPtr<BoolPromise> InitializeTemporaryClient(
      const ClientMetadata& aClientMetadata, bool aCreateIfNonExistent,
      RefPtr<UniversalDirectoryLock> aDirectoryLock);

  bool IsTemporaryClientInitialized(const ClientMetadata& aClientMetadata);

  // Returns a pair of an nsIFile object referring to the directory, and a bool
  // indicating whether the directory was newly created.
  Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
  EnsureTemporaryClientIsInitialized(const ClientMetadata& aClientMetadata,
                                     bool aCreateIfNonExistent);

  RefPtr<BoolPromise> InitializeTemporaryStorage();

  RefPtr<BoolPromise> InitializeTemporaryStorage(
      RefPtr<UniversalDirectoryLock> aDirectoryLock);

  bool IsTemporaryStorageInitialized() const {
    AssertIsOnOwningThread();

    return mTemporaryStorageInitialized;
  }

 private:
  nsresult InitializeTemporaryStorageInternal();

  nsresult EnsureTemporaryStorageIsInitializedInternal();

 public:
  RefPtr<BoolPromise> InitializeAllTemporaryOrigins();

  RefPtr<BoolPromise> SaveOriginAccessTime(
      const OriginMetadata& aOriginMetadata);

  RefPtr<BoolPromise> SaveOriginAccessTime(
      const OriginMetadata& aOriginMetadata,
      RefPtr<UniversalDirectoryLock> aDirectoryLock);

  RefPtr<OriginUsageMetadataArrayPromise> GetUsage(
      bool aGetAll, RefPtr<BoolPromise> aOnCancelPromise = nullptr);

  RefPtr<UsageInfoPromise> GetOriginUsage(
      const PrincipalInfo& aPrincipalInfo,
      RefPtr<BoolPromise> aOnCancelPromise = nullptr);

  RefPtr<UInt64Promise> GetCachedOriginUsage(
      const PrincipalInfo& aPrincipalInfo);

  RefPtr<CStringArrayPromise> ListOrigins();

  RefPtr<CStringArrayPromise> ListCachedOrigins();

  RefPtr<BoolPromise> ClearStoragesForOrigin(
      const Maybe<PersistenceType>& aPersistenceType,
      const PrincipalInfo& aPrincipalInfo);

  RefPtr<BoolPromise> ClearStoragesForClient(
      Maybe<PersistenceType> aPersistenceType,
      const PrincipalInfo& aPrincipalInfo, Client::Type aClientType);

  RefPtr<BoolPromise> ClearStoragesForOriginPrefix(
      const Maybe<PersistenceType>& aPersistenceType,
      const PrincipalInfo& aPrincipalInfo);

  RefPtr<BoolPromise> ClearStoragesForOriginAttributesPattern(
      const OriginAttributesPattern& aPattern);

  RefPtr<BoolPromise> ClearPrivateRepository();

  RefPtr<BoolPromise> ClearStorage();

  RefPtr<BoolPromise> ShutdownStoragesForOrigin(
      Maybe<PersistenceType> aPersistenceType,
      const PrincipalInfo& aPrincipalInfo);

  RefPtr<BoolPromise> ShutdownStoragesForClient(
      Maybe<PersistenceType> aPersistenceType,
      const PrincipalInfo& aPrincipalInfo, Client::Type aClientType);

  RefPtr<BoolPromise> ShutdownStorage(
      Maybe<OriginOperationCallbackOptions> aCallbackOptions = Nothing(),
      Maybe<OriginOperationCallbacks&> aCallbacks = Nothing());

  void ShutdownStorageInternal();

  // Returns a bool indicating whether the directory was newly created.
  Result<bool, nsresult> EnsureOriginDirectory(nsIFile& aDirectory);

  nsresult AboutToClearOrigins(const PersistenceScope& aPersistenceScope,
                               const OriginScope& aOriginScope,
                               const ClientStorageScope& aClientStorageScope);

  void OriginClearCompleted(const OriginMetadata& aOriginMetadata,
                            const ClientStorageScope& aClientStorageScope);

  void RepositoryClearCompleted(PersistenceType aPersistenceType);

  void StartIdleMaintenance() {
    AssertIsOnOwningThread();

    for (const auto& client : *mClients) {
      client->StartIdleMaintenance();
    }

    NotifyMaintenanceStarted(*this);
  }

  void StopIdleMaintenance() {
    AssertIsOnOwningThread();

    for (const auto& client : *mClients) {
      client->StopIdleMaintenance();
    }
  }

  void AssertCurrentThreadOwnsQuotaMutex() {
    mQuotaMutex.AssertCurrentThreadOwns();
  }

  void AssertNotCurrentThreadOwnsQuotaMutex() {
    mQuotaMutex.AssertNotCurrentThreadOwns();
  }

  nsIThread* IOThread() { return mIOThread->get(); }

  Client* GetClient(Client::Type aClientType);

  const AutoTArray<Client::Type, Client::TYPE_MAX>& AllClientTypes();

  const nsString& GetBasePath() const { return mBasePath; }

  const nsString& GetStorageName() const { return mStorageName; }

  const nsString& GetStoragePath() const { return *mStoragePath; }

  const nsString& GetStoragePath(PersistenceType aPersistenceType) const {
    if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
      return *mPermanentStoragePath;
    }

    if (aPersistenceType == PERSISTENCE_TYPE_TEMPORARY) {
      return *mTemporaryStoragePath;
    }

    if (aPersistenceType == PERSISTENCE_TYPE_DEFAULT) {
      return *mDefaultStoragePath;
    }

    MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_PRIVATE);

    return *mPrivateStoragePath;
  }

  bool IsThumbnailPrivateIdentityIdKnown() const;

  uint32_t GetThumbnailPrivateIdentityId() const;

  void SetThumbnailPrivateIdentityId(uint32_t aThumbnailPrivateIdentityId);

  uint64_t GetGroupLimit() const;
  static uint64_t GetGroupLimitForLimit(uint64_t aLimit);

  Maybe<OriginStateMetadata> GetOriginStateMetadata(
      const OriginMetadata& aOriginMetadata);

  std::pair<uint64_t, uint64_t> GetUsageAndLimitForEstimate(
      const OriginMetadata& aOriginMetadata);

  uint64_t GetOriginUsage(const PrincipalMetadata& aPrincipalMetadata);

  Maybe<FullOriginMetadata> GetFullOriginMetadata(
      const OriginMetadata& aOriginMetadata);

  /**
   * Retrieves the total number of directory iterations performed.
   *
   * @return The total count of directory iterations, which is currently
   *         incremented only during clearing operations.
   */
  uint64_t TotalDirectoryIterations() const;

  /**
   * Retrieves the number of metadata updates performed by SaveOriginAccessTime
   * operation, as tracked on the background thread. This count is incremented
   * after the operation has fully completed.
   */
  uint64_t SaveOriginAccessTimeCount() const;

  /**
   * Retrieves the number of metadata updates performed by SaveOriginAccessTime
   * operation, as tracked internally on the I/O thread. This count is
   * incremented when the actual metadata file update occurs.
   */
  uint64_t SaveOriginAccessTimeCountInternal() const;

  // Record a quota client shutdown step, if shutting down.
  // Assumes that the QuotaManager singleton is alive.
  static void MaybeRecordQuotaClientShutdownStep(
      const Client::Type aClientType, const nsACString& aStepDescription);

  // Record a quota client shutdown step, if shutting down.
  // Checks if the QuotaManager singleton is alive.
  static void SafeMaybeRecordQuotaClientShutdownStep(
      Client::Type aClientType, const nsACString& aStepDescription);

  // Record a quota manager shutdown step, use only if shutdown is active.
  void RecordQuotaManagerShutdownStep(const nsACString& aStepDescription);

  // Record a quota manager shutdown step, if shutting down.
  void MaybeRecordQuotaManagerShutdownStep(const nsACString& aStepDescription);

  template <typename F>
  void MaybeRecordQuotaManagerShutdownStepWith(F&& aFunc);

  static void GetStorageId(PersistenceType aPersistenceType,
                           const nsACString& aOrigin, Client::Type aClientType,
                           nsACString& aDatabaseId);

  static bool IsOriginInternal(const nsACString& aOrigin);

  static bool AreOriginsEqualOnDisk(const nsACString& aOrigin1,
                                    const nsACString& aOrigin2);

  // XXX This method currently expects the original origin string (not yet
  // sanitized).
  static Result<PrincipalInfo, nsresult> ParseOrigin(const nsACString& aOrigin);

  static void InvalidateQuotaCache();

  OriginMetadataArray GetTemporaryOrigins(
      PersistenceType aPersistenceType) const;

 private:
  virtual ~QuotaManager();

  nsresult Init();

  void Shutdown();

  void RegisterDirectoryLock(DirectoryLockImpl& aLock);

  void UnregisterDirectoryLock(DirectoryLockImpl& aLock);

  void AddPendingDirectoryLock(DirectoryLockImpl& aLock);

  void RemovePendingDirectoryLock(DirectoryLockImpl& aLock);

  uint64_t LockedCollectOriginsForEviction(
      uint64_t aMinSizeToBeFreed,
      nsTArray<RefPtr<OriginDirectoryLock>>& aLocks);

  void LockedRemoveQuotaForRepository(PersistenceType aPersistenceType);

  void LockedRemoveQuotaForOrigin(const OriginMetadata& aOriginMetadata);

  bool LockedHasGroupInfoPair(const nsACString& aGroup) const;

  already_AddRefed<GroupInfo> LockedGetOrCreateGroupInfo(
      PersistenceType aPersistenceType, const nsACString& aSuffix,
      const nsACString& aGroup);

  already_AddRefed<OriginInfo> LockedGetOriginInfo(
      PersistenceType aPersistenceType,
      const OriginMetadata& aOriginMetadata) const;

  nsresult UpgradeFromIndexedDBDirectoryToPersistentStorageDirectory(
      nsIFile* aIndexedDBDir);

  nsresult UpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory(
      nsIFile* aPersistentStorageDir);

  nsresult MaybeUpgradeToDefaultStorageDirectory(nsIFile& aStorageFile);

  template <typename Helper>
  nsresult UpgradeStorage(const int32_t aOldVersion, const int32_t aNewVersion,
                          mozIStorageConnection* aConnection);

  nsresult UpgradeStorageFrom0_0To1_0(mozIStorageConnection* aConnection);

  nsresult UpgradeStorageFrom1_0To2_0(mozIStorageConnection* aConnection);

  nsresult UpgradeStorageFrom2_0To2_1(mozIStorageConnection* aConnection);

  nsresult UpgradeStorageFrom2_1To2_2(mozIStorageConnection* aConnection);

  nsresult UpgradeStorageFrom2_2To2_3(mozIStorageConnection* aConnection);

  nsresult MaybeCreateOrUpgradeStorage(mozIStorageConnection& aConnection);

  OkOrErr MaybeRemoveLocalStorageArchiveTmpFile();

  nsresult MaybeRemoveLocalStorageDataAndArchive(nsIFile& aLsArchiveFile);

  nsresult MaybeRemoveLocalStorageDirectories();

  Result<Ok, nsresult> CopyLocalStorageArchiveFromWebAppsStore(
      nsIFile& aLsArchiveFile) const;

  Result<nsCOMPtr<mozIStorageConnection>, nsresult>
  CreateLocalStorageArchiveConnection(nsIFile& aLsArchiveFile) const;

  Result<nsCOMPtr<mozIStorageConnection>, nsresult>
  RecopyLocalStorageArchiveFromWebAppsStore(nsIFile& aLsArchiveFile);

  Result<nsCOMPtr<mozIStorageConnection>, nsresult>
  DowngradeLocalStorageArchive(nsIFile& aLsArchiveFile);

  Result<nsCOMPtr<mozIStorageConnection>, nsresult>
  UpgradeLocalStorageArchiveFromLessThan4To4(nsIFile& aLsArchiveFile);

  /*
  nsresult UpgradeLocalStorageArchiveFrom4To5();
  */

  Result<Ok, nsresult> MaybeCreateOrUpgradeLocalStorageArchive(
      nsIFile& aLsArchiveFile);

  Result<Ok, nsresult> CreateEmptyLocalStorageArchive(
      nsIFile& aLsArchiveFile) const;

  template <typename OriginFunc>
  Result<Ok, nsresult> InitializeOriginDirectory(
      const nsCOMPtr<nsIFile>& aChildDirectory, const nsAutoString& aLeafName,
      PersistenceType aPersistenceType,
      nsTArray<struct RenameAndInitInfo>& aRenameAndInitInfos,
      OriginFunc&& aOriginFunc);

  // Determine the type of a repository entry (directory, file, or absent)
  // and handle it accordingly.
  template <typename OriginFunc>
  Result<Ok, nsresult> ResolveRepositoryEntry(
      const nsCOMPtr<nsIFile>& aChildDirectory,
      PersistenceType aPersistenceType,
      nsTArray<RenameAndInitInfo>& aRenameAndInitInfos,
      OriginFunc&& aOriginFunc);

  template <typename OriginFunc>
  nsresult InitializeRepository(PersistenceType aPersistenceType,
                                OriginFunc&& aOriginFunc);

  nsresult InitializeOrigin(nsIFile* aDirectory,
                            const FullOriginMetadata& aFullOriginMetadata,
                            bool aForGroup = false);

  using OriginInfosFlatTraversable =
      nsTArray<NotNull<RefPtr<const OriginInfo>>>;

  using OriginInfosNestedTraversable =
      nsTArray<nsTArray<NotNull<RefPtr<const OriginInfo>>>>;

  OriginInfosNestedTraversable GetOriginInfosExceedingGroupLimit() const;

  OriginInfosNestedTraversable GetOriginInfosExceedingGlobalLimit() const;

  // Returns origins with zero usage. If aCutoffAccessTime is provided, origins
  // whose last access time is newer than the cutoff are excluded.
  //
  // The cutoff time is expressed as an int64_t value in microseconds since the
  // Unix epoch (1970-01-01 00:00:00 UTC), matching the format returned by
  // PR_Now(). This is the same time unit used throughout Quota Manager for
  // access and modification timestamps.
  //
  // Typically callers compute it as:
  //     const int64_t cutoff = PR_Now() - (N * PR_USEC_PER_SEC);
  // where N is the desired age threshold in seconds (for example, one week).
  OriginInfosNestedTraversable GetOriginInfosWithZeroUsage(
      const Maybe<int64_t>& aCutoffAccessTime = Nothing()) const;

  /**
   * Clears the given set of origins.
   *
   * @param aDoomedOriginInfos
   *   Origins to be cleared.
   * @param aChecker
   *   A callable invoked for each origin before clearing. Typically used to
   *   enforce or assert invariants at the call site.
   * @param aMaxOriginsToClear
   *   Optional cap on the number of origins cleared in a single run. If
   *   Nothing(), all doomed origins are cleared.
   */
  template <typename Checker>
  void ClearOrigins(const OriginInfosNestedTraversable& aDoomedOriginInfos,
                    Checker&& aChecker,
                    const Maybe<size_t>& aMaxOriginsToClear = Nothing());

  void CleanupTemporaryStorage();

  void RecordTemporaryStorageMetrics();

  void DeleteOriginDirectory(const OriginMetadata& aOriginMetadata);

  void FinalizeOriginEviction(nsTArray<RefPtr<OriginDirectoryLock>>&& aLocks);

  Result<Ok, nsresult> ArchiveOrigins(
      const nsTArray<FullOriginMetadata>& aFullOriginMetadatas);

  void ReleaseIOThreadObjects() {
    AssertIsOnIOThread();

    for (Client::Type type : AllClientTypes()) {
      (*mClients)[type]->ReleaseIOThreadObjects();
    }
  }

  void AddTemporaryOrigin(const FullOriginMetadata& aFullOriginMetadata);

  void RemoveTemporaryOrigin(const OriginMetadata& aOriginMetadata);

  void RemoveTemporaryOrigins(PersistenceType aPersistenceType);

  void RemoveTemporaryOrigins();

  /**
   * Retrieves the count of thumbnail private identity temporary origins.
   *
   * This method returns the current count of temporary origins associated with
   * thumbnail private identity contexts. It requires that the thumbnail
   * private identity id is known.
   *
   * @return The count of thumbnail private identity temporary origins.
   *
   * @note The thumbnail private identity id must be known before calling this
   *   method. If the id is not known, it will cause a debug assertion failure
   *   due to the `MOZ_ASSERT`.
   */
  uint32_t ThumbnailPrivateIdentityTemporaryOriginCount() const;

  PrincipalMetadataArray GetAllTemporaryGroups() const;

  OriginMetadataArray GetAllTemporaryOrigins() const;

  void NoteInitializedOrigin(PersistenceType aPersistenceType,
                             const nsACString& aOrigin);

  void NoteUninitializedOrigins(
      const OriginMetadataArray& aOriginMetadataArray);

  void NoteUninitializedRepository(PersistenceType aPersistenceType);

  bool IsOriginInitialized(PersistenceType aPersistenceType,
                           const nsACString& aOrigin) const;

  void NoteInitializedClient(PersistenceType aPersistenceType,
                             const nsACString& aOrigin,
                             Client::Type aClientType);

  void NoteUninitializedClients(
      const ClientMetadataArray& aClientMetadataArray);

  void NoteUninitializedClients(
      const OriginMetadataArray& aOriginMetadataArray);

  void NoteUninitializedClients(PersistenceType aPersistenceType);

  bool IsClientInitialized(PersistenceType aPersistenceType,
                           const nsACString& aOrigin,
                           Client::Type aClientType) const;

  bool IsSanitizedOriginValid(const nsACString& aSanitizedOrigin);

  Result<nsCString, nsresult> EnsureStorageOriginFromOrigin(
      const nsACString& aOrigin);

  Result<nsCString, nsresult> GetOriginFromStorageOrigin(
      const nsACString& aStorageOrigin);

  int64_t GenerateDirectoryLockId();

  /**
   * Registers a ClientDirectoryLockHandle for the given origin.
   *
   * Tracks the handle in internal bookkeeping. If this is the first handle
   * registered for the origin, the caller-provided update callback is invoked.
   *
   * The update callback can be used to perform first-time setup, such as
   * updating the origin’s access time.
   */
  template <typename UpdateCallback>
  void RegisterClientDirectoryLockHandle(const OriginMetadata& aOriginMetadata,
                                         UpdateCallback&& aUpdateCallback);

  /**
   * Invokes the given callback with the active OpenClientDirectoryInfo entry
   * for the specified origin.
   *
   * This method is typically used after the first handle has been registered
   * via RegisterClientDirectoryLockHandle. It provides easy access to the
   * associated OpenClientDirectoryInfo for reading and/or updating its data.
   *
   * Currently, it is primarily used in the final step of OpenClientDirectory
   * to retrieve the first-access promise returned by SaveOriginAccessTime,
   * which is stored during the first handle registration. The returned promise
   * is then used to ensure that client access is blocked until the origin
   * access time update is complete.
   */
  template <typename Callback>
  auto WithOpenClientDirectoryInfo(const OriginMetadata& aOriginMetadata,
                                   Callback&& aCallback)
      -> std::invoke_result_t<Callback, OpenClientDirectoryInfo&>;

  /**
   * Unregisters a ClientDirectoryLockHandle for the given origin.
   *
   * Decreases the active handle count and removes the internal tracking entry
   * if this was the last handle (in some shutdown cases, the entry may no
   * longer exist; this is currently tolerated, see comment in implementation).
   * If the handle being unregistered was the last one for the origin, the
   * caller-provided update callback is invoked.
   *
   * The update callback can be used to perform final cleanup, such as updating
   * the origin’s access time.
   */
  template <typename UpdateCallback>
  void UnregisterClientDirectoryLockHandle(
      const OriginMetadata& aOriginMetadata, UpdateCallback&& aUpdateCallback);

  /**
   * This wrapper is used by ClientDirectoryLockHandle to notify the
   * QuotaManager when a non-inert (i.e., owning) handle is being destroyed.
   *
   * This extra abstraction (ClientDirectoryLockHandle could call
   * UnregisterClientDirectoryLockHandle directly) enables future changes to
   * the registration methods, such as templating them. Without this wrapper,
   * such changes would require exposing their implementation in
   * QuotaManagerImpl.h, which would allow access from another translation unit.
   */
  void ClientDirectoryLockHandleDestroy(ClientDirectoryLockHandle& aHandle);

  bool ShutdownStarted() const;

  void RecordShutdownStep(Maybe<Client::Type> aClientType,
                          const nsACString& aStepDescription);

  template <typename Func>
  auto ExecuteInitialization(Initialization aInitialization, Func&& aFunc)
      -> std::invoke_result_t<Func, const FirstInitializationAttempt<
                                        Initialization, StringGenerator>&>;

  template <typename Func>
  auto ExecuteInitialization(Initialization aInitialization,
                             const nsACString& aContext, Func&& aFunc)
      -> std::invoke_result_t<Func, const FirstInitializationAttempt<
                                        Initialization, StringGenerator>&>;

  template <typename Func>
  auto ExecuteGroupInitialization(const nsACString& aGroup,
                                  const GroupInitialization aInitialization,
                                  const nsACString& aContext, Func&& aFunc)
      -> std::invoke_result_t<Func, const FirstInitializationAttempt<
                                        Initialization, StringGenerator>&>;

  template <typename Func>
  auto ExecuteOriginInitialization(const nsACString& aOrigin,
                                   const OriginInitialization aInitialization,
                                   const nsACString& aContext, Func&& aFunc)
      -> std::invoke_result_t<Func, const FirstInitializationAttempt<
                                        Initialization, StringGenerator>&>;

  /**
   * Increments the counter tracking the total number of directory iterations.
   *
   * @note This is currently called only during clearing operations to update
   *       the mTotalDirectoryIterations member.
   */
  void IncreaseTotalDirectoryIterations();

  /**
   * Increments the counter tracking SaveOriginAccessTime metadata updates,
   * recorded on the background thread after the operation has completed.
   */
  void IncreaseSaveOriginAccessTimeCount();

  /**
   * Increments the counter tracking SaveOriginAccessTime metadata updates,
   * recorded internally on the I/O thread when the metadata file is updated.
   */
  void IncreaseSaveOriginAccessTimeCountInternal();

  template <typename Collect, typename Pred>
  static OriginInfosFlatTraversable CollectLRUOriginInfosUntil(
      Collect&& aCollect, Pred&& aPred);

  // Thread on which IO is performed.
  LazyInitializedOnceNotNull<const nsCOMPtr<nsIThread>> mIOThread;

  nsCOMPtr<mozIStorageConnection> mStorageConnection;

  EnumeratedArray<Client::Type, nsCString, size_t(Client::TYPE_MAX)>
      mShutdownSteps;
  LazyInitializedOnce<const TimeStamp> mShutdownStartedAt;

  // Accesses to mQuotaManagerShutdownSteps must be protected by mQuotaMutex.
  nsCString mQuotaManagerShutdownSteps;

  mutable mozilla::Mutex mQuotaMutex MOZ_UNANNOTATED;

  nsClassHashtable<nsCStringHashKey, GroupInfoPair> mGroupInfoPairs;

  // Maintains a list of directory locks that are queued.
  nsTArray<RefPtr<DirectoryLockImpl>> mPendingDirectoryLocks;

  // Maintains a list of directory locks that are acquired or queued. It can be
  // accessed on the owning (PBackground) thread only.
  nsTArray<NotNull<DirectoryLockImpl*>> mDirectoryLocks;

  // Maintains a list of directory locks that are exclusive. This is a subset
  // of mDirectoryLocks and is used to optimize lock acquisition by allowing
  // shared locks to skip unnecessary comparisons. It is accessed only on the
  // owning (PBackground) thread.
  nsTArray<NotNull<DirectoryLockImpl*>> mExclusiveDirectoryLocks;

  // Only modifed on the owning thread, but read on multiple threads. Therefore
  // all modifications (including those on the owning thread) and all reads off
  // the owning thread must be protected by mQuotaMutex. In other words, only
  // reads on the owning thread don't have to be protected by mQuotaMutex.
  nsTHashMap<nsUint64HashKey, NotNull<DirectoryLockImpl*>>
      mDirectoryLockIdTable;

  // Things touched on the owning (PBackground) thread only.
  struct BackgroundThreadAccessible {
    PrincipalMetadataArray mUninitializedGroups;
    nsTHashSet<nsCString> mInitializedGroups;

    // Tracks active origin directories for updating origin access time.
    nsTHashMap<nsCStringHashKey, OpenClientDirectoryInfo>
        mOpenClientDirectoryInfos;

    // Tracks how many times SaveOriginAccessTime resulted in updating metadata.
    uint64_t mSaveOriginAccessTimeCount = 0;
  };
  ThreadBound<BackgroundThreadAccessible> mBackgroundThreadAccessible;

  using BoolArray = AutoTArray<bool, PERSISTENCE_TYPE_INVALID>;
  nsTHashMap<nsCStringHashKeyWithDisabledMemmove, BoolArray>
      mInitializedOrigins;

  using BitSetArray =
      AutoTArray<BitSet<Client::TYPE_MAX>, PERSISTENCE_TYPE_INVALID>;
  nsTHashMap<nsCStringHashKeyWithDisabledMemmove, BitSetArray>
      mInitializedClients;

  // Things touched on the IO thread only.
  struct IOThreadAccessible {
    nsTHashMap<nsCStringHashKey, nsTArray<FullOriginMetadata>>
        mAllTemporaryOrigins;
    Maybe<uint32_t> mThumbnailPrivateIdentityId;
    // Tracks the total number of directory iterations.
    // Note: This is currently incremented only during clearing operations.
    uint64_t mTotalDirectoryIterations = 0;
    // Tracks how many times SaveOriginAccessTime resulted in updating metadata.
    uint64_t mSaveOriginAccessTimeCount = 0;
    // Tracks the count of thumbnail private identity temporary origins.
    uint32_t mThumbnailPrivateIdentityTemporaryOriginCount = 0;
  };
  ThreadBound<IOThreadAccessible> mIOThreadAccessible;

  // A list of all successfully initialized persistent origins. This list isn't
  // protected by any mutex but it is only ever touched on the IO thread.
  nsTArray<nsCString> mInitializedOriginsInternal;

  // A hash table that is used to cache origin parser results for given
  // sanitized origin strings. This hash table isn't protected by any mutex but
  // it is only ever touched on the IO thread.
  nsTHashMap<nsCStringHashKey, bool> mValidOrigins;

  // These maps are protected by mQuotaMutex.
  nsTHashMap<nsCStringHashKey, nsCString> mOriginToStorageOriginMap;
  nsTHashMap<nsCStringHashKey, nsCString> mStorageOriginToOriginMap;

  // This array is populated at initialization time and then never modified, so
  // it can be iterated on any thread.
  LazyInitializedOnce<const AutoTArray<RefPtr<Client>, Client::TYPE_MAX>>
      mClients;

  using ClientTypesArray = AutoTArray<Client::Type, Client::TYPE_MAX>;
  LazyInitializedOnce<const ClientTypesArray> mAllClientTypes;
  LazyInitializedOnce<const ClientTypesArray> mAllClientTypesExceptLS;

  // This object isn't protected by any mutex but it is only ever touched on
  // the IO thread.
  InitializationInfo mInitializationInfo;

  const nsString mBasePath;
  const nsString mStorageName;
  LazyInitializedOnce<const nsString> mIndexedDBPath;
  LazyInitializedOnce<const nsString> mStoragePath;
  LazyInitializedOnce<const nsString> mStorageArchivesPath;
  LazyInitializedOnce<const nsString> mPermanentStoragePath;
  LazyInitializedOnce<const nsString> mTemporaryStoragePath;
  LazyInitializedOnce<const nsString> mDefaultStoragePath;
  LazyInitializedOnce<const nsString> mPrivateStoragePath;
  LazyInitializedOnce<const nsString> mToBeRemovedStoragePath;

  MozPromiseHolder<BoolPromise> mInitializeAllTemporaryOriginsPromiseHolder;

  uint64_t mTemporaryStorageLimit;
  uint64_t mTemporaryStorageUsage;
  int64_t mNextDirectoryLockId;
  bool mStorageInitialized;
  bool mPersistentStorageInitialized;
  bool mPersistentStorageInitializedInternal;
  bool mTemporaryStorageInitialized;
  bool mTemporaryStorageInitializedInternal;
  bool mInitializingAllTemporaryOrigins;
  bool mAllTemporaryOriginsInitialized;
  bool mCacheUsable;
};

}  // namespace mozilla::dom::quota

#endif /* mozilla_dom_quota_quotamanager_h_ */
