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;
}