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

#ifndef STORAGE_BROWSER_DATABASE_DATABASE_TRACKER_H_
#define STORAGE_BROWSER_DATABASE_DATABASE_TRACKER_H_

#include <stdint.h>

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

#include "base/component_export.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/gtest_prod_util.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/observer_list.h"
#include "base/strings/string_util.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/types/pass_key.h"
#include "components/services/storage/public/mojom/quota_client.mojom.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "net/base/completion_once_callback.h"
#include "storage/browser/database/database_connections.h"
#include "url/origin.h"

namespace sql {
class Database;
class MetaTable;
}

namespace storage {

class DatabaseQuotaClient;
class QuotaClientCallbackWrapper;
class QuotaManagerProxy;
class SpecialStoragePolicy;

COMPONENT_EXPORT(STORAGE_BROWSER)
extern const base::FilePath::CharType kDatabaseDirectoryName[];
COMPONENT_EXPORT(STORAGE_BROWSER)
extern const base::FilePath::CharType kTrackerDatabaseFileName[];

class DatabasesTable;

// This class is used to store information about all databases in an origin.
class COMPONENT_EXPORT(STORAGE_BROWSER) OriginInfo {
 public:
  OriginInfo();
  OriginInfo(const OriginInfo& origin_info);
  ~OriginInfo();

  const std::string& GetOriginIdentifier() const { return origin_identifier_; }
  int64_t TotalSize() const { return total_size_; }
  base::Time LastModified() const { return last_modified_; }
  void GetAllDatabaseNames(std::vector<std::u16string>* databases) const;
  int64_t GetDatabaseSize(const std::u16string& database_name) const;

 protected:
  OriginInfo(const std::string& origin_identifier, int64_t total_size);

  std::string origin_identifier_;
  int64_t total_size_;
  base::Time last_modified_;
  std::map<std::u16string, int64_t> database_sizes_;
};

// This class manages the main database and keeps track of open databases.
//
// The data in this class is not thread-safe, so all methods of this class
// should be called on the task runner returned by task_runner(). The only
// exceptions are the constructor, the destructor, and the getters explicitly
// marked as thread-safe.
class COMPONENT_EXPORT(STORAGE_BROWSER) DatabaseTracker
    : public base::RefCountedThreadSafe<DatabaseTracker> {
 public:
  REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE();

  class Observer {
   public:
    virtual void OnDatabaseSizeChanged(const std::string& origin_identifier,
                                       const std::u16string& database_name,
                                       int64_t database_size) = 0;
    virtual void OnDatabaseScheduledForDeletion(
        const std::string& origin_identifier,
        const std::u16string& database_name) = 0;

   protected:
    virtual ~Observer() = default;
  };

  static scoped_refptr<DatabaseTracker> Create(
      const base::FilePath& profile_path,
      bool is_incognito,
      scoped_refptr<SpecialStoragePolicy> special_storage_policy,
      scoped_refptr<QuotaManagerProxy> quota_manager_proxy);

  // Exposed for base::MakeRefCounted. Users should call Create().
  DatabaseTracker(const base::FilePath& profile_path,
                  bool is_incognito,
                  scoped_refptr<SpecialStoragePolicy> special_storage_policy,
                  scoped_refptr<QuotaManagerProxy> quota_manager_proxy,
                  base::PassKey<DatabaseTracker>);

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

  // Methods not explicity marked thread-safe must be called on this sequence.
  //
  // Thread-safe getter.
  base::SequencedTaskRunner* task_runner() { return task_runner_.get(); }

  void DatabaseOpened(const std::string& origin_identifier,
                      const std::u16string& database_name,
                      const std::u16string& database_details,
                      int64_t* database_size);
  void DatabaseModified(const std::string& origin_identifier,
                        const std::u16string& database_name);
  void DatabaseClosed(const std::string& origin_identifier,
                      const std::u16string& database_name);
  void HandleSqliteError(const std::string& origin_identifier,
                         const std::u16string& database_name,
                         int error);

  void CloseDatabases(const DatabaseConnections& connections);

  void AddObserver(Observer* observer);
  void RemoveObserver(Observer* observer);

  void CloseTrackerDatabaseAndClearCaches();

  // Thread-safe getter.
  const base::FilePath& database_directory() const { return db_dir_; }

#if defined(OHOS_WEBSTORAGE)
  base::FilePath GetFullDBFilePath(const std::string& origin_identifier,
                                   const std::u16string& database_name,
                                   bool suffix = false);
#else
  base::FilePath GetFullDBFilePath(const std::string& origin_identifier,
                                   const std::u16string& database_name);
#endif  // defined(OHOS_WEBSTORAGE)

  // virtual for unit-testing only
  virtual bool GetOriginInfo(const std::string& origin_id, OriginInfo* info);
  virtual bool GetAllOriginIdentifiers(std::vector<std::string>* origin_ids);
  virtual bool GetAllOriginsInfo(std::vector<OriginInfo>* origins_info);

  // Thread-safe getter.
  const scoped_refptr<QuotaManagerProxy>& quota_manager_proxy() const {
    return quota_manager_proxy_;
  }

  bool IsDatabaseScheduledForDeletion(const std::string& origin_identifier,
                                      const std::u16string& database_name);

  // Deletes a single database.
  //
  // `callback` must be non-null, and is invoked upon completion with a
  // net::Error, which will most likely be net::OK or net::FAILED. `callback`
  // may be called before this method returns.
  void DeleteDatabase(const std::string& origin_identifier,
                      const std::u16string& database_name,
                      net::CompletionOnceCallback callback);

  // Deletes databases touched since `cutoff`.
  //
  // Does not delete databases belonging to origins designated as protected by
  // the SpecialStoragePolicy passed to the DatabaseTracker constructor.
  //
  // `callback` must must be non-null, and is invoked upon completion with a
  // net::Error. The status will be net::OK on success, or net::FAILED if not
  // all databases could be deleted. `callback` may be called before this method
  // returns.
  void DeleteDataModifiedSince(const base::Time& cutoff,
                               net::CompletionOnceCallback callback);

  // Deletes all databases that belong to the given origin.
  //
  // `callback` must must be non-null, and is invoked upon completion with a
  // net::Error. The status will be net::OK on success, or net::FAILED if not
  // all databases could be deleted. `callback` may be called before this method
  // returns.
  virtual void DeleteDataForOrigin(const url::Origin& origin,
                                   net::CompletionOnceCallback callback);

  bool IsIncognitoProfile() const { return is_incognito_; }

  const base::File* GetIncognitoFile(const std::u16string& vfs_file_path) const;
  const base::File* SaveIncognitoFile(const std::u16string& vfs_file_path,
                                      base::File file);
  void CloseIncognitoFileHandle(const std::u16string& vfs_file_path);
  bool HasSavedIncognitoFileHandle(const std::u16string& vfs_file_path) const;

  // Shutdown the database tracker, deleting database files if the tracker is
  // used for an Incognito profile.
  void Shutdown();
  // Disables the exit-time deletion of session-only data.
  void SetForceKeepSessionState();

 protected:
  // Subclasses need PassKeys to call the constructor.
  static base::PassKey<DatabaseTracker> CreatePassKey() {
    return base::PassKey<DatabaseTracker>();
  }

 private:
  friend class base::RefCountedThreadSafe<DatabaseTracker>;
  friend class DatabaseTracker_TestHelper_Test;
  friend class MockDatabaseTracker;  // for testing

  using DatabaseSet = std::map<std::string, std::set<std::u16string>>;

  class CachedOriginInfo : public OriginInfo {
   public:
    CachedOriginInfo() : OriginInfo(std::string(), 0) {}
    void SetOriginIdentifier(const std::string& origin_identifier) {
      origin_identifier_ = origin_identifier;
    }
    void SetDatabaseSize(const std::u16string& database_name,
                         int64_t new_size) {
      // If the name does not exist in the map, operator[] creates a new entry
      // with a default-constructed value. The default-constructed value for
      // int64_t is zero (0), which is exactly what we want `old_size` to be set
      // to in this case.
      int64_t& database_size = database_sizes_[database_name];
      int64_t old_size = database_size;

      database_size = new_size;
      if (new_size != old_size)
        total_size_ += new_size - old_size;
    }
    void UpdateLastModified(base::Time last_modified) {
      if (last_modified > last_modified_)
        last_modified_ = last_modified;
    }
  };

  virtual ~DatabaseTracker();

  // Registers WebSQL's QuotaClient with the QuotaManager.
  void RegisterQuotaClient();

  // Deletes the directory that stores all DBs in Incognito mode, if it
  // exists.
  void DeleteIncognitoDBDirectory();

  // Deletes session-only databases. Blocks databases from being created/opened.
  void ClearSessionOnlyOrigins();

  bool DeleteClosedDatabase(const std::string& origin_identifier,
                            const std::u16string& database_name);

  // Delete all files belonging to the given origin given that no database
  // connections within this origin are open, or if |force| is true, delete
  // the meta data and rename the associated directory.
  bool DeleteOrigin(const std::string& origin_identifier, bool force);
  void DeleteDatabaseIfNeeded(const std::string& origin_identifier,
                              const std::u16string& database_name);

  bool LazyInit();
  bool UpgradeToCurrentVersion();
  void InsertOrUpdateDatabaseDetails(const std::string& origin_identifier,
                                     const std::u16string& database_name,
                                     const std::u16string& database_details);

  void ClearAllCachedOriginInfo();
  CachedOriginInfo* MaybeGetCachedOriginInfo(
      const std::string& origin_identifier,
      bool create_if_needed);
  CachedOriginInfo* GetCachedOriginInfo(
      const std::string& origin_identifier) {
    return MaybeGetCachedOriginInfo(origin_identifier, true);
  }

  int64_t GetDBFileSize(const std::string& origin_identifier,
                        const std::u16string& database_name);
  int64_t SeedOpenDatabaseInfo(const std::string& origin_identifier,
                               const std::u16string& database_name,
                               const std::u16string& description);
  int64_t UpdateOpenDatabaseInfoAndNotify(
      const std::string& origin_identifier,
      const std::u16string& database_name,
      const std::u16string* opt_description);
  int64_t UpdateOpenDatabaseSizeAndNotify(const std::string& origin_identifier,
                                          const std::u16string& database_name) {
    return UpdateOpenDatabaseInfoAndNotify(origin_identifier, database_name,
                                           nullptr);
  }

  void ScheduleDatabaseForDeletion(const std::string& origin_identifier,
                                   const std::u16string& database_name);
  // Schedule a set of open databases for deletion. If non-null, callback is
  // invoked upon completion.
  void ScheduleDatabasesForDeletion(const DatabaseSet& databases,
                                    net::CompletionOnceCallback callback);

  // Returns the directory where all DB files for the given origin are stored.
  base::FilePath GetOriginDirectory(const std::string& origin_identifier);

  bool is_initialized_ = false;
  const bool is_incognito_;
  bool force_keep_session_state_ = false;
  bool shutting_down_ = false;
  const base::FilePath profile_path_;

  // Can be accessed from any thread via database_directory().
  //
  // Thread-safety argument: The member is immutable.
  const base::FilePath db_dir_;

  std::unique_ptr<sql::Database> db_;
  std::unique_ptr<DatabasesTable> databases_table_;
  std::unique_ptr<sql::MetaTable> meta_table_;
  base::ObserverList<Observer, true>::Unchecked observers_;
  std::map<std::string, CachedOriginInfo> origins_info_map_;
  DatabaseConnections database_connections_;

  // The set of databases that should be deleted but are still opened
  DatabaseSet dbs_to_be_deleted_;
  std::vector<std::pair<net::CompletionOnceCallback, DatabaseSet>>
      deletion_callbacks_;

  // Apps and Extensions can have special rights.
  const scoped_refptr<SpecialStoragePolicy> special_storage_policy_;

  // Can be accessed from any thread via quota_manager_proxy().
  //
  // Thread-safety argument: The reference is immutable.
  const scoped_refptr<QuotaManagerProxy> quota_manager_proxy_;

  // Sequence where file I/O is allowed.
  const scoped_refptr<base::SequencedTaskRunner> task_runner_;

  std::unique_ptr<DatabaseQuotaClient> quota_client_;
  std::unique_ptr<storage::QuotaClientCallbackWrapper> quota_client_wrapper_;

  // When in Incognito mode, store a DELETE_ON_CLOSE handle to each
  // main DB and journal file that was accessed. When the Incognito profile
  // goes away (or when the browser crashes), all these handles will be
  // closed, and the files will be deleted.
  std::map<std::u16string, base::File*> incognito_file_handles_;

  // In a non-Incognito profile, all DBs in an origin are stored in a
  // directory named after the origin. In an Incognito profile though, we do
  // not want the directory structure to reveal the origins visited by the user
  // (in case the browser process crashes and those directories are not
  // deleted). So we use this map to assign directory names that do not reveal
  // this information.
  std::map<std::string, std::u16string> incognito_origin_directories_;
  int incognito_origin_directories_generator_ = 0;

  mojo::Receiver<mojom::QuotaClient> quota_client_receiver_;

  FRIEND_TEST_ALL_PREFIXES(DatabaseTracker, TestHelper);
};

}  // namespace storage

#endif  // STORAGE_BROWSER_DATABASE_DATABASE_TRACKER_H_