std::vector SyncTree::RemoveEventRegistration()

in database/src/desktop/core/sync_tree.cc [602:699]


std::vector<Event> SyncTree::RemoveEventRegistration(
    const QuerySpec& query_spec, void* listener_ptr, Error cancel_error) {
  std::vector<Event> cancel_events;
  persistence_manager_->RunInTransaction([&]() {
    // Find the sync_point first. Then deal with whether or not it has matching
    // listeners
    SyncPoint* maybe_sync_point = sync_point_tree_.GetValueAt(query_spec.path);

    // A removal on a default query affects all queries at that location. A
    // removal on an indexed query, even one without other query constraints,
    // does *not* affect all queries at that location. So this check must be for
    // QuerySpecIsDefault(), and not QuerySpecLoadsAllData().
    if (maybe_sync_point != nullptr &&
        (QuerySpecIsDefault(query_spec) ||
         maybe_sync_point->ViewExistsForQuery(query_spec))) {
      std::vector<QuerySpec> removed;
      cancel_events = maybe_sync_point->RemoveEventRegistration(
          query_spec, listener_ptr, cancel_error, &removed);
      if (maybe_sync_point->IsEmpty()) {
        Tree<SyncPoint>* subtree = sync_point_tree_.GetChild(query_spec.path);
        if (subtree) subtree->value().reset();
      }

      // We may have just removed one of many listeners and can short-circuit
      // this whole process. We may also not have removed a default listener, in
      // which case all of the descendant listeners should already be properly
      // set up.
      //
      // Since indexed queries can shadow if they don't have other query
      // constraints, check for QuerySpecLoadsAllData(), instead of
      // QuerySpecIsDefault().
      bool removing_default = false;
      for (QuerySpec& query_removed : removed) {
        persistence_manager_->SetQueryInactive(query_spec);
        removing_default |= QuerySpecLoadsAllData(query_removed);
      }
      Tree<SyncPoint>* current_tree = &sync_point_tree_;
      bool covered = current_tree->value().has_value() &&
                     current_tree->value()->HasCompleteView();
      for (const std::string& directory : query_spec.path.GetDirectories()) {
        current_tree = current_tree->GetChild(directory);
        covered = covered || (current_tree->value().has_value() &&
                              current_tree->value()->HasCompleteView());
        if (covered || current_tree->IsEmpty()) {
          break;
        }
      }

      if (removing_default && !covered) {
        Tree<SyncPoint>* subtree = sync_point_tree_.GetChild(query_spec.path);
        // There are potentially child listeners. Determine what if any listens
        // we need to send before executing the removal.
        if (!subtree->IsEmpty()) {
          // We need to fold over our subtree and collect the listeners to send
          std::vector<const View*> new_views;
          CollectDistinctViewsForSubTree(subtree, &new_views);

          // Ok, we've collected all the listens we need. Set them up.
          for (const View* view : new_views) {
            QuerySpec new_query = view->query_spec();
            listen_provider_->StartListening(QuerySpecForListening(new_query),
                                             TagForQuerySpec(new_query), view);
          }
        } else {
          // There's nothing below us, so nothing we need to start listening on
        }
      }

      // If we removed anything and we're not covered by a higher up listen, we
      // need to stop listening on this query. The above block has us covered in
      // terms of making sure we're set up on listens lower in the tree.  Also,
      // note that if we have a cancelError, it's already been removed at the
      // provider level.
      if (!covered && !removed.empty() && cancel_error == kErrorNone) {
        // If we removed a default, then we weren't listening on any of the
        // other queries here. Just cancel the one default. Otherwise, we need
        // to iterate through and cancel each individual query
        if (removing_default) {
          listen_provider_->StopListening(QuerySpecForListening(query_spec),
                                          Tag());
        } else {
          for (const QuerySpec& query_to_remove : removed) {
            Tag tag = TagForQuerySpec(query_to_remove);
            FIREBASE_DEV_ASSERT(tag.has_value());
            listen_provider_->StopListening(
                QuerySpecForListening(query_to_remove), tag);
          }
        }
      }
      // Now, clear all of the tags we're tracking for the removed listens.
      RemoveTags(removed);
    } else {
      // No-op, this listener must've been already removed.
    }
    return true;
  });
  return cancel_events;
}