// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_DATABASE_H_
#define CHROME_BROWSER_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_DATABASE_H_

#include <string>
#include <vector>

#include "base/basictypes.h"
#include "base/files/file_path.h"
#include "base/gtest_prod_util.h"
#include "base/memory/ref_counted_memory.h"
#include "base/synchronization/lock.h"
#include "base/timer/timer.h"
#include "chrome/browser/extensions/activity_log/activity_actions.h"
#include "chrome/common/extensions/extension.h"
#include "content/public/browser/browser_thread.h"
#include "sql/connection.h"
#include "sql/init_status.h"

namespace base {
class Clock;
class FilePath;
}

namespace extensions {

// Encapsulates the SQL connection for the activity log database.  This class
// holds the database connection and has methods for writing.  All of the
// methods except the constructor need to be called on the DB thread.
//
// Object ownership and lifetime is a bit complicated for ActivityLog,
// ActivityLogPolicy, and ActivityDatabase:
//
//    ActivityLog ----> ActivityLogPolicy ----> ActivityDatabase
//                         ^                               |
//                         |                               |
//                         \--(ActivityDatabase::Delegate)-/
//
// The ActivityLogPolicy object contains a pointer to the ActivityDatabase, and
// the ActivityDatabase contains a pointer back to the ActivityLogPolicy object
// (as an instance of ActivityDatabase::Delegate).
//
// Since some cleanup must happen on the database thread, deletion should occur
// as follows:
//   1. ActivityLog calls ActivityLogPolicy::Close()
//   2. ActivityLogPolicy should call ActivityDatabase::Close() on the database
//      thread.
//   3. ActivityDatabase::Close() shuts down the database, then calls
//      ActivityDatabase::Delegate::OnDatabaseClose().
//   4. ActivityDatabase::Delegate::OnDatabaseClose() should delete the
//      ActivityLogPolicy object.
//   5. ActivityDatabase::Close() finishes running by deleting the
//      ActivityDatabase object.
//
// (This assumes the common case that the ActivityLogPolicy uses an
// ActivityDatabase and implements the ActivityDatabase::Delegate interface.
// It is also possible for an ActivityLogPolicy to not use a database at all,
// in which case ActivityLogPolicy::Close() should directly delete itself.)
class ActivityDatabase {
 public:
  // Interface defining calls that the ActivityDatabase can make into a
  // ActivityLogPolicy instance to implement policy-specific behavior.  Methods
  // are always invoked on the database thread.  Classes other than
  // ActivityDatabase should not call these methods.
  class Delegate {
   protected:
    friend class ActivityDatabase;

    // A Delegate is never directly deleted; it should instead delete itself
    // after any final cleanup when OnDatabaseClose() is invoked.
    virtual ~Delegate() {}

    // Initializes the database schema; this gives a policy a chance to create
    // or update database tables as needed.  Should return true on success.
    // Will be called from within a database transaction.
    virtual bool InitDatabase(sql::Connection* db) = 0;

    // Requests that the policy flush any pending actions to the database.
    // Should return true on success or false on a database error.  Will not be
    // called from a transaction (the implementation may wish to use a
    // transaction for the flush).
    virtual bool FlushDatabase(sql::Connection* db) = 0;

    // Called if the database encounters a permanent error; the policy should
    // not expect to make any future writes to the database and may want to
    // discard any queued data.
    virtual void OnDatabaseFailure() = 0;

    // Called by ActivityDatabase just before the ActivityDatabase object is
    // deleted.  The database will make no further callbacks after invoking
    // this method, so it is an appropriate time for the policy to delete
    // itself.
    virtual void OnDatabaseClose() = 0;
  };

  // Value to be passed to AdviseFlush below to force a database flush.
  static const int kFlushImmediately = -1;

  // Need to call Init to actually use the ActivityDatabase.  The Delegate
  // provides hooks for an ActivityLogPolicy to control the database schema and
  // reads/writes.
  explicit ActivityDatabase(Delegate* delegate);

  // Opens the DB.  This invokes OnDatabaseInit in the delegate to create or
  // update the database schema if needed.
  void Init(const base::FilePath& db_name);

  // An ActivityLogPolicy should call this to kill the ActivityDatabase.
  void Close();

  // Inform the database that there may be additional data which could be
  // written out.  The size parameter should indicate (approximately) how many
  // records are queued to be written; the database may use this information to
  // schedule a flush early if too much data is queueing up.  A value of
  // kFlushImmediately will force an immediate call into
  // Delegate::FlushDatabase(); otherwise, it is up to the database to
  // determine when to flush.
  void AdviseFlush(int size);

  // Turns off batch I/O writing mode. This should only be used in unit tests,
  // browser tests, or in our special --enable-extension-activity-log-testing
  // policy state.
  void SetBatchModeForTesting(bool batch_mode);

  bool is_db_valid() const { return valid_db_; }

  // A helper method for initializing or upgrading a database table.  The
  // content_fields array should list the names of all of the columns in the
  // database. The field_types should specify the types of the corresponding
  // columns (e.g., INTEGER or LONGVARCHAR). There should be the same number of
  // field_types as content_fields, since the two arrays should correspond.
  static bool InitializeTable(sql::Connection* db,
                              const char* table_name,
                              const char* content_fields[],
                              const char* field_types[],
                              const int num_content_fields);

  // Runs the given callback, passing it a handle to the database connection.
  // If the database is not valid, the callback is run (to allow it to do any
  // needed cleanup) but passed a NULL value.
  void RunOnDatabase(const base::Callback<void(sql::Connection*)>& callback);

 private:
  // This should never be invoked by another class. Use Close() to order a
  // suicide.
  virtual ~ActivityDatabase();

  // Used by the Init() method as a convenience for handling a failed database
  // initialization attempt. Prints an error and puts us in the soft failure
  // state.
  void LogInitFailure();

  // When we're in batched mode (which is on by default), we write to the db
  // every X minutes instead of on every API call. This prevents the annoyance
  // of writing to disk multiple times a second.
  void RecordBatchedActions();

  // If an error is unrecoverable or occurred while we were trying to close
  // the database properly, we take "emergency" actions: break any outstanding
  // transactions, raze the database, and close. When next opened, the
  // database will be empty.
  void HardFailureClose();

  // Doesn't actually close the DB, but changes bools to prevent further writes
  // or changes to it.
  void SoftFailureClose();

  // Handle errors in database writes. For a serious & permanent error, it
  // invokes HardFailureClose(); for a less serious/permanent error, it invokes
  // SoftFailureClose().
  void DatabaseErrorCallback(int error, sql::Statement* stmt);

  // For unit testing only.
  void RecordBatchedActionsWhileTesting();
  void SetTimerForTesting(int milliseconds);

  // Retrieve a handle to the raw SQL database.  This is only intended to be
  // used by ActivityLogDatabasePolicy::GetDatabaseConnection(), and should
  // only be called on the database thread.
  sql::Connection* GetSqlConnection();

  // A reference a Delegate for policy-specific database behavior.  See the
  // top-level comment for ActivityDatabase for comments on cleanup.
  Delegate* delegate_;

  sql::Connection db_;
  bool valid_db_;
  bool batch_mode_;
  base::TimeDelta batching_period_;
  base::RepeatingTimer<ActivityDatabase> timer_;
  bool already_closed_;
  bool did_init_;

  friend class ActivityLogDatabasePolicy;
  FRIEND_TEST_ALL_PREFIXES(ActivityDatabaseTest, BatchModeOff);
  FRIEND_TEST_ALL_PREFIXES(ActivityDatabaseTest, BatchModeOn);
  FRIEND_TEST_ALL_PREFIXES(ActivityDatabaseTest, BatchModeFlush);
  DISALLOW_COPY_AND_ASSIGN(ActivityDatabase);
};

}  // namespace extensions
#endif  // CHROME_BROWSER_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_DATABASE_H_