Commit 1c6947d7 authored by Mohamed Amir Yosef's avatar Mohamed Amir Yosef Committed by Commit Bot

[Sync] Rename ProcessorEntityTracker to ProcessorEntity

This is the first step in the effort of refactoring the
ClientTagBasedModelTypeProcessor.

Bug: 947044
Change-Id: I18acf64ba4edbd8e87cef08754be9460384db078
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1543510
Commit-Queue: Mohamed Amir Yosef <mamir@chromium.org>
Auto-Submit: Mohamed Amir Yosef <mamir@chromium.org>
Reviewed-by: default avatarMikel Astiz <mastiz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#645363}
parent 9e777c2a
......@@ -457,8 +457,8 @@ jumbo_static_library("sync") {
"model_impl/model_type_store_impl.h",
"model_impl/model_type_store_service_impl.cc",
"model_impl/model_type_store_service_impl.h",
"model_impl/processor_entity_tracker.cc",
"model_impl/processor_entity_tracker.h",
"model_impl/processor_entity.cc",
"model_impl/processor_entity.h",
"model_impl/proxy_model_type_controller_delegate.cc",
"model_impl/proxy_model_type_controller_delegate.h",
"model_impl/sync_metadata_store_change_list.cc",
......@@ -930,7 +930,7 @@ source_set("unit_tests") {
"model_impl/in_memory_metadata_change_list_unittest.cc",
"model_impl/model_type_store_backend_unittest.cc",
"model_impl/model_type_store_impl_unittest.cc",
"model_impl/processor_entity_tracker_unittest.cc",
"model_impl/processor_entity_unittest.cc",
"model_impl/syncable_service_based_bridge_unittest.cc",
"protocol/proto_enum_conversions_unittest.cc",
"protocol/proto_value_conversions_unittest.cc",
......
......@@ -11,7 +11,7 @@
namespace syncer {
class ProcessorEntityTracker;
class ProcessorEntity;
struct EntityData;
namespace syncable {
......@@ -91,7 +91,7 @@ class ProtoValuePtr {
private:
friend struct syncable::EntryKernel;
friend struct EntityData;
friend class ProcessorEntityTracker;
friend class ProcessorEntity;
FRIEND_TEST_ALL_PREFIXES(ProtoValuePtrTest, ValueAssignment);
FRIEND_TEST_ALL_PREFIXES(ProtoValuePtrTest, ValueSwap);
FRIEND_TEST_ALL_PREFIXES(ProtoValuePtrTest, SharingTest);
......
......@@ -101,7 +101,7 @@ struct EntityData {
// Makes a copy of EntityData and updates its id to |new_id|. This is needed
// when entity id is updated with commit response while EntityData for next
// local change is cached in ProcessorEntityTracker.
// local change is cached in ProcessorEntity.
EntityDataPtr UpdateId(const std::string& new_id) const WARN_UNUSED_RESULT;
// Makes a copy of EntityData and updates its client tag hash to
......
......@@ -33,7 +33,7 @@
namespace syncer {
class CommitQueue;
class ProcessorEntityTracker;
class ProcessorEntity;
// A sync component embedded on the model type's thread that tracks entity
// metadata in the model store and coordinates communication between sync and
......@@ -130,13 +130,13 @@ class ClientTagBasedModelTypeProcessor : public ModelTypeProcessor,
// Helper function to process the update for a single entity. If a local data
// change is required, it will be added to |entity_changes|. The return value
// is the tracker for this entity, or nullptr if the update should be ignored.
ProcessorEntityTracker* ProcessUpdate(const UpdateResponseData& update,
// is the tracked entity, or nullptr if the update should be ignored.
ProcessorEntity* ProcessUpdate(const UpdateResponseData& update,
EntityChangeList* entity_changes);
// Resolve a conflict between |update| and the pending commit in |entity|.
ConflictResolution::Type ResolveConflict(const UpdateResponseData& update,
ProcessorEntityTracker* entity,
ProcessorEntity* entity,
EntityChangeList* changes);
// Recommit all entities for encryption except those in |already_updated|.
......@@ -170,7 +170,7 @@ class ClientTagBasedModelTypeProcessor : public ModelTypeProcessor,
std::unordered_set<std::string> storage_keys_to_load,
std::unique_ptr<DataBatch> data_batch);
// Caches EntityData from the |data_batch| in the entity trackers and checks
// Caches EntityData from the |data_batch| in the entity and checks
// that every entity in |storage_keys_to_load| was successfully loaded (or is
// not tracked by the processor any more). Reports failed checks to UMA.
void ConsumeDataBatch(std::unordered_set<std::string> storage_keys_to_load,
......@@ -192,26 +192,24 @@ class ClientTagBasedModelTypeProcessor : public ModelTypeProcessor,
const EntityData& data) const;
// Gets the entity for the given storage key, or null if there isn't one.
ProcessorEntityTracker* GetEntityForStorageKey(
const std::string& storage_key);
const ProcessorEntityTracker* GetEntityForStorageKey(
ProcessorEntity* GetEntityForStorageKey(const std::string& storage_key);
const ProcessorEntity* GetEntityForStorageKey(
const std::string& storage_key) const;
// Gets the entity for the given tag hash, or null if there isn't one.
ProcessorEntityTracker* GetEntityForTagHash(const std::string& tag_hash);
const ProcessorEntityTracker* GetEntityForTagHash(
const std::string& tag_hash) const;
ProcessorEntity* GetEntityForTagHash(const std::string& tag_hash);
const ProcessorEntity* GetEntityForTagHash(const std::string& tag_hash) const;
// Create an entity in the entity map for |storage_key| and return a pointer
// to it.
// Requires that no entity for |storage_key| already exists in the map.
ProcessorEntityTracker* CreateEntity(const std::string& storage_key,
ProcessorEntity* CreateEntity(const std::string& storage_key,
const EntityData& data);
// Version of the above that generates a tag for |data|.
ProcessorEntityTracker* CreateEntity(const EntityData& data);
ProcessorEntity* CreateEntity(const EntityData& data);
// Returns true if all processor entity trackers have non-empty storage keys.
// Returns true if all processor entities have non-empty storage keys.
bool AllStorageKeysPopulated() const;
// Expires entries according to garbage collection directives.
......@@ -243,9 +241,9 @@ class ClientTagBasedModelTypeProcessor : public ModelTypeProcessor,
void ExpireEntriesByItemLimit(int32_t max_number_of_items,
MetadataChangeList* metadata_changes);
// Removes entity tracker and clears metadata for entity from
// MetadataChangeList.
void RemoveEntity(ProcessorEntityTracker* entity,
// Removes |entity| and clears metadata for |entity| from
// |metadata_change_list|.
void RemoveEntity(ProcessorEntity* entity,
MetadataChangeList* metadata_change_list);
// Resets the internal state of the processor to the one right after calling
......@@ -314,7 +312,7 @@ class ClientTagBasedModelTypeProcessor : public ModelTypeProcessor,
// A map of client tag hash to sync entities known to this processor. This
// should contain entries and metadata for most everything, although the
// entities may not always contain model type data/specifics.
std::map<std::string, std::unique_ptr<ProcessorEntityTracker>> entities_;
std::map<std::string, std::unique_ptr<ProcessorEntity>> entities_;
// The bridge wants to communicate entirely via storage keys that it is free
// to define and can understand more easily. All of the sync machinery wants
......
......@@ -271,8 +271,7 @@ class ClientTagBasedModelTypeProcessorTest : public ::testing::Test {
return;
}
ProcessorEntityTracker* GetEntityForStorageKey(
const std::string& storage_key) {
ProcessorEntity* GetEntityForStorageKey(const std::string& storage_key) {
return type_processor()->GetEntityForStorageKey(storage_key);
}
......@@ -1949,9 +1948,9 @@ TEST_F(ClientTagBasedModelTypeProcessorTest,
worker()->VerifyNthPendingCommit(1, {kHash1}, {specifics2});
}
// Tests that UpdateStorageKey propagates storage key to ProcessorEntityTracker
// Tests that UpdateStorageKey propagates storage key to ProcessorEntity
// and updates corresponding entity's metadata in MetadataChangeList, and
// UntrackEntity will remove corresponding ProcessorEntityTracker and do not add
// UntrackEntity will remove corresponding ProcessorEntity and do not add
// any entity's metadata into MetadataChangeList.
TEST_F(ClientTagBasedModelTypeProcessorTest, ShouldUpdateStorageKey) {
// Setup bridge to not support calls to GetStorageKey. This will cause
......@@ -2010,7 +2009,7 @@ TEST_F(ClientTagBasedModelTypeProcessorTest,
}
// Tests that UntrackEntity won't propagate storage key to
// ProcessorEntityTracker, and no entity's metadata are added into
// ProcessorEntity, and no entity's metadata are added into
// MetadataChangeList.
TEST_F(ClientTagBasedModelTypeProcessorTest, ShouldUntrackEntity) {
// Setup bridge to not support calls to GetStorageKey. This will cause
......@@ -2026,7 +2025,7 @@ TEST_F(ClientTagBasedModelTypeProcessorTest, ShouldUntrackEntity) {
EXPECT_EQ(1, bridge()->merge_call_count());
EXPECT_EQ(0U, ProcessorEntityCount());
// Metadata should not be written under kUntrackKey1. This means that
// UntrackEntity was called and corresponding ProcessorEntityTracker is
// UntrackEntity was called and corresponding ProcessorEntity is
// removed and no storage key got propagated to MetadataChangeList.
EXPECT_FALSE(db()->HasMetadata(kHash1));
EXPECT_EQ(0U, db()->metadata_count());
......@@ -2034,7 +2033,7 @@ TEST_F(ClientTagBasedModelTypeProcessorTest, ShouldUntrackEntity) {
}
// Tests that UntrackEntityForStorage won't propagate storage key to
// ProcessorEntityTracker, and no entity's metadata are added into
// ProcessorEntity, and no entity's metadata are added into
// MetadataChangeList.
TEST_F(ClientTagBasedModelTypeProcessorTest, ShouldUntrackEntityForStorageKey) {
InitializeToReadyState();
......
......@@ -2,9 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sync/model_impl/processor_entity_tracker.h"
#include "components/sync/model_impl/processor_entity.h"
#include "base/base64.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/sha1.h"
......@@ -31,7 +32,7 @@ void HashSpecifics(const sync_pb::EntitySpecifics& specifics,
} // namespace
std::unique_ptr<ProcessorEntityTracker> ProcessorEntityTracker::CreateNew(
std::unique_ptr<ProcessorEntity> ProcessorEntity::CreateNew(
const std::string& storage_key,
const std::string& client_tag_hash,
const std::string& id,
......@@ -46,20 +47,17 @@ std::unique_ptr<ProcessorEntityTracker> ProcessorEntityTracker::CreateNew(
metadata.set_server_version(kUncommittedVersion);
metadata.set_creation_time(TimeToProtoTime(creation_time));
return std::unique_ptr<ProcessorEntityTracker>(
new ProcessorEntityTracker(storage_key, &metadata));
return base::WrapUnique(new ProcessorEntity(storage_key, &metadata));
}
std::unique_ptr<ProcessorEntityTracker>
ProcessorEntityTracker::CreateFromMetadata(const std::string& storage_key,
std::unique_ptr<ProcessorEntity> ProcessorEntity::CreateFromMetadata(
const std::string& storage_key,
sync_pb::EntityMetadata* metadata) {
DCHECK(!storage_key.empty());
return std::unique_ptr<ProcessorEntityTracker>(
new ProcessorEntityTracker(storage_key, metadata));
return base::WrapUnique(new ProcessorEntity(storage_key, metadata));
}
ProcessorEntityTracker::ProcessorEntityTracker(
const std::string& storage_key,
ProcessorEntity::ProcessorEntity(const std::string& storage_key,
sync_pb::EntityMetadata* metadata)
: storage_key_(storage_key),
commit_requested_sequence_number_(metadata->acked_sequence_number()) {
......@@ -68,15 +66,15 @@ ProcessorEntityTracker::ProcessorEntityTracker(
metadata_.Swap(metadata);
}
ProcessorEntityTracker::~ProcessorEntityTracker() {}
ProcessorEntity::~ProcessorEntity() {}
void ProcessorEntityTracker::SetStorageKey(const std::string& storage_key) {
void ProcessorEntity::SetStorageKey(const std::string& storage_key) {
DCHECK(storage_key_.empty());
DCHECK(!storage_key.empty());
storage_key_ = storage_key;
}
void ProcessorEntityTracker::SetCommitData(EntityData* data) {
void ProcessorEntity::SetCommitData(EntityData* data) {
DCHECK(data);
// Update data's fields from metadata.
data->client_tag_hash = metadata_.client_tag_hash();
......@@ -89,17 +87,17 @@ void ProcessorEntityTracker::SetCommitData(EntityData* data) {
CacheCommitData(data->PassToPtr());
}
void ProcessorEntityTracker::CacheCommitData(const EntityDataPtr& data_ptr) {
void ProcessorEntity::CacheCommitData(const EntityDataPtr& data_ptr) {
DCHECK(RequiresCommitData());
commit_data_ = data_ptr;
DCHECK(HasCommitData());
}
bool ProcessorEntityTracker::HasCommitData() const {
bool ProcessorEntity::HasCommitData() const {
return !commit_data_->client_tag_hash.empty();
}
bool ProcessorEntityTracker::MatchesData(const EntityData& data) const {
bool ProcessorEntity::MatchesData(const EntityData& data) const {
if (metadata_.is_deleted())
return data.is_deleted();
if (data.is_deleted())
......@@ -107,7 +105,7 @@ bool ProcessorEntityTracker::MatchesData(const EntityData& data) const {
return MatchesSpecificsHash(data.specifics);
}
bool ProcessorEntityTracker::MatchesBaseData(const EntityData& data) const {
bool ProcessorEntity::MatchesBaseData(const EntityData& data) const {
DCHECK(IsUnsynced());
if (data.is_deleted() || metadata_.base_specifics_hash().empty()) {
return false;
......@@ -117,27 +115,27 @@ bool ProcessorEntityTracker::MatchesBaseData(const EntityData& data) const {
return hash == metadata_.base_specifics_hash();
}
bool ProcessorEntityTracker::IsUnsynced() const {
bool ProcessorEntity::IsUnsynced() const {
return metadata_.sequence_number() > metadata_.acked_sequence_number();
}
bool ProcessorEntityTracker::RequiresCommitRequest() const {
bool ProcessorEntity::RequiresCommitRequest() const {
return metadata_.sequence_number() > commit_requested_sequence_number_;
}
bool ProcessorEntityTracker::RequiresCommitData() const {
bool ProcessorEntity::RequiresCommitData() const {
return RequiresCommitRequest() && !HasCommitData() && !metadata_.is_deleted();
}
bool ProcessorEntityTracker::CanClearMetadata() const {
bool ProcessorEntity::CanClearMetadata() const {
return metadata_.is_deleted() && !IsUnsynced();
}
bool ProcessorEntityTracker::UpdateIsReflection(int64_t update_version) const {
bool ProcessorEntity::UpdateIsReflection(int64_t update_version) const {
return metadata_.server_version() >= update_version;
}
void ProcessorEntityTracker::RecordEntityUpdateLatency(int64_t update_version,
void ProcessorEntity::RecordEntityUpdateLatency(int64_t update_version,
const ModelType& type) {
auto first_greater =
unsynced_time_per_committed_server_version_.upper_bound(update_version);
......@@ -159,8 +157,7 @@ void ProcessorEntityTracker::RecordEntityUpdateLatency(int64_t update_version,
unsynced_time_per_committed_server_version_.begin(), first_greater);
}
void ProcessorEntityTracker::RecordIgnoredUpdate(
const UpdateResponseData& update) {
void ProcessorEntity::RecordIgnoredUpdate(const UpdateResponseData& update) {
DCHECK(metadata_.server_id().empty() ||
metadata_.server_id() == update.entity->id);
metadata_.set_server_id(update.entity->id);
......@@ -176,8 +173,7 @@ void ProcessorEntityTracker::RecordIgnoredUpdate(
}
}
void ProcessorEntityTracker::RecordAcceptedUpdate(
const UpdateResponseData& update) {
void ProcessorEntity::RecordAcceptedUpdate(const UpdateResponseData& update) {
DCHECK(!IsUnsynced());
RecordIgnoredUpdate(update);
metadata_.set_is_deleted(update.entity->is_deleted());
......@@ -186,8 +182,7 @@ void ProcessorEntityTracker::RecordAcceptedUpdate(
UpdateSpecificsHash(update.entity->specifics);
}
void ProcessorEntityTracker::RecordForcedUpdate(
const UpdateResponseData& update) {
void ProcessorEntity::RecordForcedUpdate(const UpdateResponseData& update) {
DCHECK(IsUnsynced());
// There was a conflict and the server just won it. Explicitly ack all
// pending commits so they are never enqueued again.
......@@ -196,7 +191,7 @@ void ProcessorEntityTracker::RecordForcedUpdate(
RecordAcceptedUpdate(update);
}
void ProcessorEntityTracker::MakeLocalChange(std::unique_ptr<EntityData> data) {
void ProcessorEntity::MakeLocalChange(std::unique_ptr<EntityData> data) {
DCHECK(!metadata_.client_tag_hash().empty());
// Update metadata fields from updated data.
......@@ -218,7 +213,7 @@ void ProcessorEntityTracker::MakeLocalChange(std::unique_ptr<EntityData> data) {
SetCommitData(data.get());
}
bool ProcessorEntityTracker::Delete() {
bool ProcessorEntity::Delete() {
IncrementSequenceNumber(base::Time::Now());
metadata_.set_modification_time(TimeToProtoTime(base::Time::Now()));
metadata_.set_is_deleted(true);
......@@ -240,8 +235,7 @@ bool ProcessorEntityTracker::Delete() {
metadata_.acked_sequence_number());
}
void ProcessorEntityTracker::InitializeCommitRequestData(
CommitRequestData* request) {
void ProcessorEntity::InitializeCommitRequestData(CommitRequestData* request) {
if (!metadata_.is_deleted()) {
DCHECK(HasCommitData());
DCHECK_EQ(commit_data_->client_tag_hash, metadata_.client_tag_hash());
......@@ -265,8 +259,7 @@ void ProcessorEntityTracker::InitializeCommitRequestData(
commit_requested_sequence_number_ = metadata_.sequence_number();
}
void ProcessorEntityTracker::ReceiveCommitResponse(
const CommitResponseData& data,
void ProcessorEntity::ReceiveCommitResponse(const CommitResponseData& data,
bool commit_only) {
DCHECK_EQ(metadata_.client_tag_hash(), data.client_tag_hash);
DCHECK_GT(data.sequence_number, metadata_.acked_sequence_number());
......@@ -302,14 +295,13 @@ void ProcessorEntityTracker::ReceiveCommitResponse(
}
}
void ProcessorEntityTracker::ClearTransientSyncState() {
void ProcessorEntity::ClearTransientSyncState() {
// If we have any unacknowledged commit requests outstanding, they've been
// dropped and we should forget about them.
commit_requested_sequence_number_ = metadata_.acked_sequence_number();
}
void ProcessorEntityTracker::IncrementSequenceNumber(
base::Time modification_time) {
void ProcessorEntity::IncrementSequenceNumber(base::Time modification_time) {
DCHECK(metadata_.has_sequence_number());
if (!IsUnsynced()) {
// Update the base specifics hash if this entity wasn't already out of sync.
......@@ -320,7 +312,7 @@ void ProcessorEntityTracker::IncrementSequenceNumber(
DCHECK(IsUnsynced());
}
size_t ProcessorEntityTracker::EstimateMemoryUsage() const {
size_t ProcessorEntity::EstimateMemoryUsage() const {
using base::trace_event::EstimateMemoryUsage;
size_t memory_usage = 0;
memory_usage += EstimateMemoryUsage(storage_key_);
......@@ -331,7 +323,7 @@ size_t ProcessorEntityTracker::EstimateMemoryUsage() const {
return memory_usage;
}
bool ProcessorEntityTracker::MatchesSpecificsHash(
bool ProcessorEntity::MatchesSpecificsHash(
const sync_pb::EntitySpecifics& specifics) const {
DCHECK(!metadata_.is_deleted());
DCHECK_GT(specifics.ByteSize(), 0);
......@@ -340,7 +332,7 @@ bool ProcessorEntityTracker::MatchesSpecificsHash(
return hash == metadata_.specifics_hash();
}
void ProcessorEntityTracker::UpdateSpecificsHash(
void ProcessorEntity::UpdateSpecificsHash(
const sync_pb::EntitySpecifics& specifics) {
if (specifics.ByteSize() > 0) {
HashSpecifics(specifics, metadata_.mutable_specifics_hash());
......
......@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_SYNC_MODEL_IMPL_PROCESSOR_ENTITY_TRACKER_H_
#define COMPONENTS_SYNC_MODEL_IMPL_PROCESSOR_ENTITY_TRACKER_H_
#ifndef COMPONENTS_SYNC_MODEL_IMPL_PROCESSOR_ENTITY_H_
#define COMPONENTS_SYNC_MODEL_IMPL_PROCESSOR_ENTITY_H_
#include <stdint.h>
......@@ -25,10 +25,10 @@ struct UpdateResponseData;
// of each entity with its type. It can be considered a helper class internal to
// the processor. It manages the metadata for its entity and caches entity data
// upon a local change until commit confirmation is received.
class ProcessorEntityTracker {
class ProcessorEntity {
public:
// Construct an instance representing a new locally-created item.
static std::unique_ptr<ProcessorEntityTracker> CreateNew(
static std::unique_ptr<ProcessorEntity> CreateNew(
const std::string& storage_key,
const std::string& client_tag_hash,
const std::string& id,
......@@ -36,11 +36,11 @@ class ProcessorEntityTracker {
// Construct an instance representing an item loaded from storage on init.
// This method swaps out the contents of |metadata|.
static std::unique_ptr<ProcessorEntityTracker> CreateFromMetadata(
static std::unique_ptr<ProcessorEntity> CreateFromMetadata(
const std::string& storage_key,
sync_pb::EntityMetadata* metadata);
~ProcessorEntityTracker();
~ProcessorEntity();
const std::string& storage_key() const { return storage_key_; }
const sync_pb::EntityMetadata& metadata() const { return metadata_; }
......@@ -108,7 +108,7 @@ class ProcessorEntityTracker {
// Update storage_key_. Allows setting storage key for datatypes that don't
// generate storage key from syncer::EntityData. Should only be called for
// tracker initialized with empty storage key.
// an entity initialized with empty storage key.
void SetStorageKey(const std::string& storage_key);
// Takes the passed commit data updates its fields with values from metadata
......@@ -137,10 +137,10 @@ class ProcessorEntityTracker {
size_t EstimateMemoryUsage() const;
private:
friend class ProcessorEntityTrackerTest;
friend class ProcessorEntityTest;
// The constructor swaps the data from the passed metadata.
ProcessorEntityTracker(const std::string& storage_key,
ProcessorEntity(const std::string& storage_key,
sync_pb::EntityMetadata* metadata);
// Check whether |specifics| matches the stored specifics_hash.
......@@ -171,4 +171,4 @@ class ProcessorEntityTracker {
} // namespace syncer
#endif // COMPONENTS_SYNC_MODEL_IMPL_PROCESSOR_ENTITY_TRACKER_H_
#endif // COMPONENTS_SYNC_MODEL_IMPL_PROCESSOR_ENTITY_H_
......@@ -106,7 +106,7 @@ progresses through 3 states worth noting:
## Entity Tracker
The [`ProcessorEntityTracker`][PET] tracks the state of individual entities for
The [`ProcessorEntity`][PET] tracks the state of individual entities for
the processor. It keeps the [`EntityMetadata`][EM] proto in memory, as well as
any pending commit data until it gets acked by the server. It also stores the
special `commit_requested_sequence_number_`, which tracks the sequence number of
......@@ -116,5 +116,5 @@ The tracker holds the metadata in memory forever, which is needed so we know
what to update the on-disk memory with when we get a new local or remote change.
Changing this would require being able to handle updates asynchronously.
[PET]: https://cs.chromium.org/chromium/src/components/sync/model_impl/processor_entity_tracker.h
[PET]: https://cs.chromium.org/chromium/src/components/sync/model_impl/processor_entity.h
[EM]: https://cs.chromium.org/chromium/src/components/sync/protocol/entity_metadata.proto
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