root/sync/internal_api/change_reorder_buffer.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. Traversal
  2. ExpandToInclude
  3. top
  4. begin_children
  5. end_children
  6. PushAddedItem
  7. PushDeletedItem
  8. PushUpdatedItem
  9. SetExtraDataForId
  10. SetSpecificsForId
  11. Clear
  12. IsEmpty
  13. GetAllChangesInTreeOrder

// Copyright 2012 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.

#include "sync/internal_api/change_reorder_buffer.h"

#include <limits>
#include <queue>
#include <set>
#include <utility>  // for pair<>

#include "sync/internal_api/public/base_node.h"
#include "sync/internal_api/public/base_transaction.h"
#include "sync/syncable/entry.h"
#include "sync/syncable/syncable_base_transaction.h"

using std::numeric_limits;
using std::pair;
using std::queue;
using std::set;

namespace syncer {

// Traversal provides a way to collect a set of nodes from the syncable
// directory structure and then traverse them, along with any intermediate
// nodes, in a top-down fashion, starting from a single common ancestor.  A
// Traversal starts out empty and is grown by means of the ExpandToInclude
// method.  Once constructed, the top(), begin_children(), and end_children()
// methods can be used to explore the nodes in root-to-leaf order.
class ChangeReorderBuffer::Traversal {
 public:
  typedef pair<int64, int64> ParentChildLink;
  typedef set<ParentChildLink> LinkSet;

  Traversal() : top_(kInvalidId) { }

  // Expand the traversal so that it includes the node indicated by
  // |child_handle|.
  void ExpandToInclude(syncable::BaseTransaction* trans,
                       int64 child_handle) {
    // If |top_| is invalid, this is the first insertion -- easy.
    if (top_ == kInvalidId) {
      top_ = child_handle;
      return;
    }

    int64 node_to_include = child_handle;
    while (node_to_include != kInvalidId && node_to_include != top_) {
      int64 node_parent = 0;

      syncable::Entry node(trans, syncable::GET_BY_HANDLE, node_to_include);
      CHECK(node.good());
      if (node.GetId().IsRoot()) {
        // If we've hit the root, and the root isn't already in the tree
        // (it would have to be |top_| if it were), start a new expansion
        // upwards from |top_| to unite the original traversal with the
        // path we just added that goes from |child_handle| to the root.
        node_to_include = top_;
        top_ = node.GetMetahandle();
      } else {
        // Otherwise, get the parent ID so that we can add a ParentChildLink.
        syncable::Entry parent(trans, syncable::GET_BY_ID,
                               node.GetParentId());
        CHECK(parent.good());
        node_parent = parent.GetMetahandle();

        ParentChildLink link(node_parent, node_to_include);

        // If the link exists in the LinkSet |links_|, we don't need to search
        // any higher; we are done.
        if (links_.find(link) != links_.end())
          return;

        // Otherwise, extend |links_|, and repeat on the parent.
        links_.insert(link);
        node_to_include = node_parent;
      }
    }
  }

  // Return the top node of the traversal.  Use this as a starting point
  // for walking the tree.
  int64 top() const { return top_; }

  // Return an iterator corresponding to the first child (in the traversal)
  // of the node specified by |parent_id|.  Iterate this return value until
  // it is equal to the value returned by end_children(parent_id).  The
  // enumeration thus provided is unordered.
  LinkSet::const_iterator begin_children(int64 parent_id) const {
    return links_.upper_bound(
        ParentChildLink(parent_id, numeric_limits<int64>::min()));
  }

  // Return an iterator corresponding to the last child in the traversal
  // of the node specified by |parent_id|.
  LinkSet::const_iterator end_children(int64 parent_id) const {
    return begin_children(parent_id + 1);
  }

 private:
  // The topmost point in the directory hierarchy that is in the traversal,
  // and thus the first node to be traversed.  If the traversal is empty,
  // this is kInvalidId.  If the traversal contains exactly one member, |top_|
  // will be the solitary member, and |links_| will be empty.
  int64 top_;
  // A set of single-level links that compose the traversal below |top_|.  The
  // (parent, child) ordering of values enables efficient lookup of children
  // given the parent handle, which is used for top-down traversal.  |links_|
  // is expected to be connected -- every node that appears as a parent in a
  // link must either appear as a child of another link, or else be the
  // topmost node, |top_|.
  LinkSet links_;

  DISALLOW_COPY_AND_ASSIGN(Traversal);
};

ChangeReorderBuffer::ChangeReorderBuffer() {
}

ChangeReorderBuffer::~ChangeReorderBuffer() {
}

void ChangeReorderBuffer::PushAddedItem(int64 id) {
  operations_[id] = ChangeRecord::ACTION_ADD;
}

void ChangeReorderBuffer::PushDeletedItem(int64 id) {
  operations_[id] = ChangeRecord::ACTION_DELETE;
}

void ChangeReorderBuffer::PushUpdatedItem(int64 id) {
  operations_[id] = ChangeRecord::ACTION_UPDATE;
}

void ChangeReorderBuffer::SetExtraDataForId(
    int64 id,
    ExtraPasswordChangeRecordData* extra) {
  extra_data_[id] = make_linked_ptr<ExtraPasswordChangeRecordData>(extra);
}

void ChangeReorderBuffer::SetSpecificsForId(
    int64 id,
    const sync_pb::EntitySpecifics& specifics) {
  specifics_[id] = specifics;
}

void ChangeReorderBuffer::Clear() {
  operations_.clear();
}

bool ChangeReorderBuffer::IsEmpty() const {
  return operations_.empty();
}

bool ChangeReorderBuffer::GetAllChangesInTreeOrder(
    const BaseTransaction* sync_trans,
    ImmutableChangeRecordList* changes) {
  syncable::BaseTransaction* trans = sync_trans->GetWrappedTrans();

  // Step 1: Iterate through the operations, doing three things:
  // (a) Push deleted items straight into the |changelist|.
  // (b) Construct a traversal spanning all non-deleted items.
  // (c) Construct a set of all parent nodes of any position changes.
  Traversal traversal;

  ChangeRecordList changelist;

  OperationMap::const_iterator i;
  for (i = operations_.begin(); i != operations_.end(); ++i) {
    if (i->second == ChangeRecord::ACTION_DELETE) {
      ChangeRecord record;
      record.id = i->first;
      record.action = i->second;
      if (specifics_.find(record.id) != specifics_.end())
        record.specifics = specifics_[record.id];
      if (extra_data_.find(record.id) != extra_data_.end())
        record.extra = extra_data_[record.id];
      changelist.push_back(record);
    } else {
      traversal.ExpandToInclude(trans, i->first);
    }
  }

  // Step 2: Breadth-first expansion of the traversal.
  queue<int64> to_visit;
  to_visit.push(traversal.top());
  while (!to_visit.empty()) {
    int64 next = to_visit.front();
    to_visit.pop();

    // If the node has an associated action, output a change record.
    i = operations_.find(next);
    if (i != operations_.end()) {
      ChangeRecord record;
      record.id = next;
      record.action = i->second;
      if (specifics_.find(record.id) != specifics_.end())
        record.specifics = specifics_[record.id];
      if (extra_data_.find(record.id) != extra_data_.end())
        record.extra = extra_data_[record.id];
      changelist.push_back(record);
    }

    // Now add the children of |next| to |to_visit|.
    Traversal::LinkSet::const_iterator j = traversal.begin_children(next);
    Traversal::LinkSet::const_iterator end = traversal.end_children(next);
    for (; j != end; ++j) {
      CHECK(j->first == next);
      to_visit.push(j->second);
    }
  }

  *changes = ImmutableChangeRecordList(&changelist);
  return true;
}

}  // namespace syncer

/* [<][>][^][v][top][bottom][index][help] */