Commit 513eb557 authored by peter's avatar peter Committed by Commit bot

Store the actual notification data in the notification database.

This patch introduces the actual functionality for storing notification
data in the NotificationDatabase. There are three separate methods
now, ReadNotificationData() and WriteNotificationData() and
DeleteNotificationData(),, which do exactly what their names imply.

These methods are covered by a variety of unit tests, but still aren't
hooked up to any production code.

Design document:
  http://goo.gl/TciXVp

BUG=447628

Review URL: https://codereview.chromium.org/999933002

Cr-Commit-Position: refs/heads/master@{#320761}
parent e6c776d0
......@@ -8,15 +8,23 @@
#include "base/files/file_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "content/browser/notifications/notification_database_data.h"
#include "content/public/browser/browser_thread.h"
#include "storage/common/database/database_identifier.h"
#include "third_party/leveldatabase/src/helpers/memenv/memenv.h"
#include "third_party/leveldatabase/src/include/leveldb/db.h"
#include "third_party/leveldatabase/src/include/leveldb/env.h"
#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
#include "url/gurl.h"
// Notification LevelDB database schema (in alphabetized order)
// =======================
//
// key: "DATA:" <origin identifier> '\x00' <notification_id>
// value: String containing the NotificationDatabaseDataProto protocol buffer
// in serialized form.
//
// key: "NEXT_NOTIFICATION_ID"
// value: Decimal string which fits into an int64_t.
//
......@@ -26,6 +34,10 @@ namespace {
// Keys of the fields defined in the database.
const char kNextNotificationIdKey[] = "NEXT_NOTIFICATION_ID";
const char kDataKeyPrefix[] = "DATA:";
// Separates the components of compound keys.
const char kKeySeparator = '\x00';
// The first notification id which to be handed out by the database.
const int64_t kFirstNotificationId = 1;
......@@ -43,11 +55,19 @@ NotificationDatabase::Status LevelDBStatusToStatus(
return NotificationDatabase::STATUS_ERROR_FAILED;
}
// Creates the compound data key in which notification data is stored.
std::string CreateDataKey(int64_t notification_id, const GURL& origin) {
return base::StringPrintf("%s%s%c%s",
kDataKeyPrefix,
storage::GetIdentifierFromOrigin(origin).c_str(),
kKeySeparator,
base::Int64ToString(notification_id).c_str());
}
} // namespace
NotificationDatabase::NotificationDatabase(const base::FilePath& path)
: path_(path),
state_(STATE_UNINITIALIZED) {
: path_(path) {
sequence_checker_.DetachFromSequence();
}
......@@ -85,37 +105,79 @@ NotificationDatabase::Status NotificationDatabase::Open(
state_ = STATE_INITIALIZED;
db_.reset(db);
return STATUS_OK;
return ReadNextNotificationId();
}
NotificationDatabase::Status NotificationDatabase::GetNextNotificationId(
int64_t* notification_id) const {
NotificationDatabase::Status NotificationDatabase::ReadNotificationData(
int64_t notification_id,
const GURL& origin,
NotificationDatabaseData* notification_database_data) const {
DCHECK(sequence_checker_.CalledOnValidSequencedThread());
DCHECK_EQ(state_, STATE_INITIALIZED);
DCHECK(notification_id);
DCHECK_EQ(STATE_INITIALIZED, state_);
DCHECK_GE(notification_id, kFirstNotificationId);
DCHECK(origin.is_valid());
DCHECK(notification_database_data);
std::string key = CreateDataKey(notification_id, origin);
std::string serialized_data;
std::string value;
Status status = LevelDBStatusToStatus(
db_->Get(leveldb::ReadOptions(), kNextNotificationIdKey, &value));
db_->Get(leveldb::ReadOptions(), key, &serialized_data));
if (status != STATUS_OK)
return status;
if (status == STATUS_ERROR_NOT_FOUND) {
*notification_id = kFirstNotificationId;
if (notification_database_data->ParseFromString(serialized_data))
return STATUS_OK;
DLOG(ERROR) << "Unable to deserialize data for notification "
<< notification_id << " belonging to " << origin << ".";
return STATUS_ERROR_CORRUPTED;
}
NotificationDatabase::Status NotificationDatabase::WriteNotificationData(
const GURL& origin,
const NotificationDatabaseData& notification_database_data,
int64_t* notification_id) {
DCHECK(sequence_checker_.CalledOnValidSequencedThread());
DCHECK_EQ(STATE_INITIALIZED, state_);
DCHECK(notification_id);
DCHECK(origin.is_valid());
std::string serialized_data;
if (!notification_database_data.SerializeToString(&serialized_data)) {
DLOG(ERROR) << "Unable to serialize data for a notification belonging "
<< "to: " << origin;
return STATUS_ERROR_FAILED;
}
DCHECK_GE(next_notification_id_, kFirstNotificationId);
leveldb::WriteBatch batch;
batch.Put(CreateDataKey(next_notification_id_, origin), serialized_data);
batch.Put(kNextNotificationIdKey,
base::Int64ToString(next_notification_id_ + 1));
Status status = LevelDBStatusToStatus(
db_->Write(leveldb::WriteOptions(), &batch));
if (status != STATUS_OK)
return status;
int64_t next_notification_id;
if (!base::StringToInt64(value, &next_notification_id) ||
next_notification_id < kFirstNotificationId) {
return STATUS_ERROR_CORRUPTED;
}
*notification_id = next_notification_id;
*notification_id = next_notification_id_++;
return STATUS_OK;
}
NotificationDatabase::Status NotificationDatabase::DeleteNotificationData(
int64_t notification_id,
const GURL& origin) {
DCHECK(sequence_checker_.CalledOnValidSequencedThread());
DCHECK_EQ(STATE_INITIALIZED, state_);
DCHECK_GE(notification_id, kFirstNotificationId);
DCHECK(origin.is_valid());
std::string key = CreateDataKey(notification_id, origin);
return LevelDBStatusToStatus(db_->Delete(leveldb::WriteOptions(), key));
}
NotificationDatabase::Status NotificationDatabase::Destroy() {
DCHECK(sequence_checker_.CalledOnValidSequencedThread());
......@@ -134,13 +196,25 @@ NotificationDatabase::Status NotificationDatabase::Destroy() {
leveldb::DestroyDB(path_.AsUTF8Unsafe(), options));
}
void NotificationDatabase::WriteNextNotificationId(
leveldb::WriteBatch* batch,
int64_t next_notification_id) const {
DCHECK_GE(next_notification_id, kFirstNotificationId);
DCHECK(batch);
NotificationDatabase::Status NotificationDatabase::ReadNextNotificationId() {
std::string value;
Status status = LevelDBStatusToStatus(
db_->Get(leveldb::ReadOptions(), kNextNotificationIdKey, &value));
batch->Put(kNextNotificationIdKey, base::Int64ToString(next_notification_id));
if (status == STATUS_ERROR_NOT_FOUND) {
next_notification_id_ = kFirstNotificationId;
return STATUS_OK;
}
if (status != STATUS_OK)
return status;
if (!base::StringToInt64(value, &next_notification_id_) ||
next_notification_id_ < kFirstNotificationId) {
return STATUS_ERROR_CORRUPTED;
}
return STATUS_OK;
}
} // namespace content
......@@ -12,6 +12,8 @@
#include "base/sequence_checker.h"
#include "content/common/content_export.h"
class GURL;
namespace leveldb {
class DB;
class Env;
......@@ -20,6 +22,8 @@ class WriteBatch;
namespace content {
struct NotificationDatabaseData;
// Implementation of the persistent notification database.
//
// This class can be constructed on any thread, but method calls must only be
......@@ -32,8 +36,8 @@ class CONTENT_EXPORT NotificationDatabase {
enum Status {
STATUS_OK = 0,
// The database, or the key associated with the operation, could not be
// found.
// The database, a notification, or a LevelDB key associated with the
// operation could not be found.
STATUS_ERROR_NOT_FOUND = 1,
// The database, or data in the database, could not be parsed as valid data.
......@@ -52,9 +56,27 @@ class CONTENT_EXPORT NotificationDatabase {
// |create_if_missing| determines whether to create the database if necessary.
Status Open(bool create_if_missing);
// Returns whether the next available notification id could be read, and
// stores the id in |notification_id| if the read was successful.
Status GetNextNotificationId(int64_t* notification_id) const;
// Reads the notification data for the notification identified by
// |notification_id| and belonging to |origin| from the database, and stores
// it in |notification_database_data|. Returns the status code.
Status ReadNotificationData(
int64_t notification_id,
const GURL& origin,
NotificationDatabaseData* notification_database_data) const;
// Writes the |notification_database_data| for a new notification belonging to
// |origin| to the database, and returns the status code of the writing
// operation. The id of the new notification will be set in |notification_id|.
Status WriteNotificationData(
const GURL& origin,
const NotificationDatabaseData& notification_database_data,
int64_t* notification_id);
// Deletes all data associated with the notification identified by
// |notification_id| belonging to |origin| from the database. Returns the
// status code of the deletion operation. Note that it is not considered a
// failure if the to-be-deleted notification does not exist.
Status DeleteNotificationData(int64_t notification_id, const GURL& origin);
// Completely destroys the contents of this database.
Status Destroy();
......@@ -70,9 +92,10 @@ class CONTENT_EXPORT NotificationDatabase {
STATE_DISABLED,
};
// Writes the next available notification id as a put operation to |batch|.
void WriteNextNotificationId(leveldb::WriteBatch* batch,
int64_t next_notification_id) const;
// Reads the next available notification id from the database and returns
// the status code of the reading operation. The value will be stored in
// the |next_notification_id_| member.
Status ReadNextNotificationId();
// Returns whether the database has been opened.
bool IsOpen() const { return db_ != nullptr; }
......@@ -86,12 +109,14 @@ class CONTENT_EXPORT NotificationDatabase {
base::FilePath path_;
int64_t next_notification_id_ = 0;
// The declaration order for these members matters, as |db_| depends on |env_|
// and thus has to be destructed first.
scoped_ptr<leveldb::Env> env_;
scoped_ptr<leveldb::DB> db_;
State state_;
State state_ = STATE_UNINITIALIZED;
base::SequenceChecker sequence_checker_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment