mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-28 19:27:10 +00:00
#1376 remove_deep_batched()
This commit is contained in:
@@ -179,6 +179,14 @@ class file(object):
|
|||||||
:rtype: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
return self.wrapped_data.remove(inst.wrapped_data)
|
return self.wrapped_data.remove(inst.wrapped_data)
|
||||||
|
|
||||||
|
def batch(self):
|
||||||
|
"""Low-level mechanism to speed up deletion of large subgraphs"""
|
||||||
|
return self.wrapped_data.batch()
|
||||||
|
|
||||||
|
def unbatch(self):
|
||||||
|
"""Low-level mechanism to speed up deletion of large subgraphs"""
|
||||||
|
return self.wrapped_data.unbatch()
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return iter(self[id] for id in self.wrapped_data.entity_names())
|
return iter(self[id] for id in self.wrapped_data.entity_names())
|
||||||
|
|||||||
@@ -126,6 +126,13 @@ def remove_deep(ifc_file, element):
|
|||||||
ifc_file.remove(ref)
|
ifc_file.remove(ref)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_deep_batched(ifc_file, element):
|
||||||
|
# @todo maybe some sort of try-finally mechanism.
|
||||||
|
ifc_file.batch()
|
||||||
|
remove_deep(ifc_file, element)
|
||||||
|
ifc_file.unbatch()
|
||||||
|
|
||||||
|
|
||||||
def get_representation(element, context, subcontext=None, target_view=None):
|
def get_representation(element, context, subcontext=None, target_view=None):
|
||||||
if element.is_a("IfcProduct") and element.Representation:
|
if element.is_a("IfcProduct") and element.Representation:
|
||||||
for r in element.Representation.Representations:
|
for r in element.Representation.Representations:
|
||||||
|
|||||||
@@ -131,6 +131,11 @@ private:
|
|||||||
void initialize_(IfcParse::IfcSpfStream* f);
|
void initialize_(IfcParse::IfcSpfStream* f);
|
||||||
|
|
||||||
void build_inverses_(IfcUtil::IfcBaseClass*);
|
void build_inverses_(IfcUtil::IfcBaseClass*);
|
||||||
|
|
||||||
|
std::set<int> batch_deletion_ids_;
|
||||||
|
bool batch_mode_ = false;
|
||||||
|
void process_deletion_();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
IfcParse::IfcSpfLexer* tokens;
|
IfcParse::IfcSpfLexer* tokens;
|
||||||
IfcParse::IfcSpfStream* stream;
|
IfcParse::IfcSpfStream* stream;
|
||||||
@@ -226,6 +231,9 @@ public:
|
|||||||
IfcUtil::IfcBaseClass* addEntity(IfcUtil::IfcBaseClass* entity);
|
IfcUtil::IfcBaseClass* addEntity(IfcUtil::IfcBaseClass* entity);
|
||||||
void addEntities(IfcEntityList::ptr es);
|
void addEntities(IfcEntityList::ptr es);
|
||||||
|
|
||||||
|
void batch() { batch_mode_ = true; }
|
||||||
|
void unbatch() { process_deletion_(); batch_mode_ = false; }
|
||||||
|
|
||||||
/// Removes entity instance from file and unsets references.
|
/// Removes entity instance from file and unsets references.
|
||||||
///
|
///
|
||||||
/// Attention when running removeEntity inside a loop over a list of entities to be removed.
|
/// Attention when running removeEntity inside a loop over a list of entities to be removed.
|
||||||
|
|||||||
+203
-153
@@ -1710,73 +1710,76 @@ IfcUtil::IfcBaseClass* IfcFile::addEntity(IfcUtil::IfcBaseClass* entity) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The mapping by entity type is updated.
|
// The mapping by entity type is updated.
|
||||||
const IfcParse::declaration* ty = &new_entity->declaration();
|
const IfcParse::declaration* ty = &new_entity->declaration();
|
||||||
|
|
||||||
if (ty->as_entity()) {
|
if (ty->as_entity()) {
|
||||||
IfcEntityList::ptr insts = instances_by_type_excl_subtypes(ty);
|
IfcEntityList::ptr insts = instances_by_type_excl_subtypes(ty);
|
||||||
if (!insts) {
|
if (!insts) {
|
||||||
insts = IfcEntityList::ptr(new IfcEntityList());
|
insts = IfcEntityList::ptr(new IfcEntityList());
|
||||||
bytype_excl[ty] = insts;
|
bytype_excl[ty] = insts;
|
||||||
}
|
}
|
||||||
insts->push(new_entity);
|
insts->push(new_entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; ty->as_entity();) {
|
||||||
|
IfcEntityList::ptr insts = instances_by_type(ty);
|
||||||
|
if (!insts) {
|
||||||
|
insts = IfcEntityList::ptr(new IfcEntityList());
|
||||||
|
bytype[ty] = insts;
|
||||||
|
}
|
||||||
|
insts->push(new_entity);
|
||||||
|
|
||||||
|
const IfcParse::declaration* pt = ty->as_entity()->supertype();
|
||||||
|
if (pt) {
|
||||||
|
ty = pt;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ty->as_entity()) {
|
||||||
|
int new_id = -1;
|
||||||
|
if (!new_entity->data().file) {
|
||||||
|
// For newly created entities ensure a valid ENTITY_INSTANCE_NAME is set
|
||||||
|
new_entity->data().file = this;
|
||||||
|
new_id = new_entity->data().set_id();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
new_id = new_entity->data().id();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; ty->as_entity();) {
|
if (byid.find(new_id) != byid.end()) {
|
||||||
IfcEntityList::ptr insts = instances_by_type(ty);
|
// This should not happen
|
||||||
if (!insts) {
|
std::stringstream ss;
|
||||||
insts = IfcEntityList::ptr(new IfcEntityList());
|
ss << "Overwriting entity with id " << new_id;
|
||||||
bytype[ty] = insts;
|
Logger::Message(Logger::LOG_WARNING, ss.str());
|
||||||
}
|
|
||||||
insts->push(new_entity);
|
|
||||||
|
|
||||||
const IfcParse::declaration* pt = ty->as_entity()->supertype();
|
|
||||||
if (pt) {
|
|
||||||
ty = pt;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ty->as_entity()) {
|
// The mapping by entity instance name is updated.
|
||||||
int new_id = -1;
|
byid[new_id] = new_entity;
|
||||||
if (!new_entity->data().file) {
|
}
|
||||||
// For newly created entities ensure a valid ENTITY_INSTANCE_NAME is set
|
|
||||||
new_entity->data().file = this;
|
|
||||||
new_id = new_entity->data().set_id();
|
|
||||||
} else {
|
|
||||||
new_id = new_entity->data().id();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (byid.find(new_id) != byid.end()) {
|
if (parsing_complete_ && ty->as_entity()) {
|
||||||
// This should not happen
|
build_inverses_(new_entity);
|
||||||
std::stringstream ss;
|
}
|
||||||
ss << "Overwriting entity with id " << new_id;
|
|
||||||
Logger::Message(Logger::LOG_WARNING, ss.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// The mapping by entity instance name is updated.
|
return new_entity;
|
||||||
byid[new_id] = new_entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parsing_complete_ && ty->as_entity()) {
|
|
||||||
build_inverses_(new_entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new_entity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void IfcFile::removeEntity(IfcUtil::IfcBaseClass* entity) {
|
void IfcFile::removeEntity(IfcUtil::IfcBaseClass* entity) {
|
||||||
const unsigned id = entity->data().id();
|
const unsigned id = entity->data().id();
|
||||||
|
|
||||||
IfcUtil::IfcBaseClass* file_entity = instance_by_id(id);
|
IfcUtil::IfcBaseClass* file_entity = instance_by_id(id);
|
||||||
|
|
||||||
// Attention when running removeEntity inside a loop over a list of entities to be removed.
|
// Attention when running removeEntity inside a loop over a list of entities to be removed.
|
||||||
// This invalidates the iterator. A workaround is to reverse the loop:
|
// This invalidates the iterator. A workaround is to reverse the loop:
|
||||||
// boost::shared_ptr<IfcEntityList> entities = ...;
|
// boost::shared_ptr<IfcEntityList> entities = ...;
|
||||||
// for (auto it = entities->end() - 1; it >= entities->begin(); --it) {
|
// for (auto it = entities->end() - 1; it >= entities->begin(); --it) {
|
||||||
// IfcUtil::IfcBaseClass *const inst = *it;
|
// IfcUtil::IfcBaseClass *const inst = *it;
|
||||||
// model->removeEntity(inst);
|
// model->removeEntity(inst);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// TODO: Create a set of weak relations. Inverse relations that do not dictate an
|
// TODO: Create a set of weak relations. Inverse relations that do not dictate an
|
||||||
// instance to be retained. For example: when deleting an IfcRepresentation, the
|
// instance to be retained. For example: when deleting an IfcRepresentation, the
|
||||||
@@ -1789,133 +1792,180 @@ void IfcFile::removeEntity(IfcUtil::IfcBaseClass* entity) {
|
|||||||
throw IfcParse::IfcException("Instance not part of this file");
|
throw IfcParse::IfcException("Instance not part of this file");
|
||||||
}
|
}
|
||||||
|
|
||||||
IfcEntityList::ptr references = instances_by_reference(id);
|
batch_deletion_ids_.insert(id);
|
||||||
|
|
||||||
// Alter entity instances with INVERSE relations to the entity being
|
if (!batch_mode_) {
|
||||||
// deleted. This is necessary to maintain a valid IFC file, because
|
process_deletion_();
|
||||||
// dangling references to it's entities name should be removed. At this
|
}
|
||||||
// moment, inversely related instances affected by the removal of the
|
}
|
||||||
// entity being deleted are not deleted themselves.
|
|
||||||
if (references) {
|
|
||||||
for (IfcEntityList::it iit = references->begin(); iit != references->end(); ++iit) {
|
|
||||||
IfcUtil::IfcBaseEntity* related_instance = (IfcUtil::IfcBaseEntity*) *iit;
|
|
||||||
|
|
||||||
|
void IfcFile::process_deletion_() {
|
||||||
|
|
||||||
for (unsigned i = 0; i < related_instance->data().getArgumentCount(); ++i) {
|
for (auto& id : batch_deletion_ids_) {
|
||||||
Argument* attr = related_instance->data().getArgument(i);
|
auto entity = instance_by_id(id);
|
||||||
if (attr->isNull()) continue;
|
|
||||||
|
|
||||||
IfcUtil::ArgumentType attr_type = attr->type();
|
IfcEntityList::ptr references = instances_by_reference(id);
|
||||||
switch(attr_type) {
|
|
||||||
case IfcUtil::Argument_ENTITY_INSTANCE: {
|
|
||||||
IfcUtil::IfcBaseClass* instance_attribute = *attr;
|
|
||||||
if (instance_attribute == entity) {
|
|
||||||
IfcWrite::IfcWriteArgument* copy = new IfcWrite::IfcWriteArgument();
|
|
||||||
copy->set(boost::blank());
|
|
||||||
related_instance->data().setArgument(i, copy);
|
|
||||||
} }
|
|
||||||
break;
|
|
||||||
case IfcUtil::Argument_AGGREGATE_OF_ENTITY_INSTANCE: {
|
|
||||||
IfcEntityList::ptr instance_list = *attr;
|
|
||||||
if (instance_list->contains(entity)) {
|
|
||||||
instance_list->remove(entity);
|
|
||||||
|
|
||||||
IfcWrite::IfcWriteArgument* copy = new IfcWrite::IfcWriteArgument();
|
// Alter entity instances with INVERSE relations to the entity being
|
||||||
copy->set(instance_list);
|
// deleted. This is necessary to maintain a valid IFC file, because
|
||||||
related_instance->data().setArgument(i, copy);
|
// dangling references to it's entities name should be removed. At this
|
||||||
} }
|
// moment, inversely related instances affected by the removal of the
|
||||||
break;
|
// entity being deleted are not deleted themselves.
|
||||||
case IfcUtil::Argument_AGGREGATE_OF_AGGREGATE_OF_ENTITY_INSTANCE: {
|
if (references) {
|
||||||
IfcEntityListList::ptr instance_list_list = *attr;
|
for (IfcEntityList::it iit = references->begin(); iit != references->end(); ++iit) {
|
||||||
if (instance_list_list->contains(entity)) {
|
IfcUtil::IfcBaseEntity* related_instance = (IfcUtil::IfcBaseEntity*) *iit;
|
||||||
IfcEntityListList::ptr new_list(new IfcEntityListList);
|
|
||||||
for (IfcEntityListList::outer_it it = instance_list_list->begin(); it != instance_list_list->end(); ++it) {
|
if (std::find(batch_deletion_ids_.begin(), batch_deletion_ids_.end(), related_instance->data().id()) != batch_deletion_ids_.end()) {
|
||||||
std::vector<IfcUtil::IfcBaseClass*> instances = *it;
|
continue;
|
||||||
std::vector<IfcUtil::IfcBaseClass*>::iterator jt;
|
}
|
||||||
while ((jt = std::find(instances.begin(), instances.end(), entity)) != instances.end()) {
|
|
||||||
instances.erase(jt);
|
for (unsigned i = 0; i < related_instance->data().getArgumentCount(); ++i) {
|
||||||
|
Argument* attr = related_instance->data().getArgument(i);
|
||||||
|
if (attr->isNull()) continue;
|
||||||
|
|
||||||
|
IfcUtil::ArgumentType attr_type = attr->type();
|
||||||
|
switch (attr_type) {
|
||||||
|
case IfcUtil::Argument_ENTITY_INSTANCE: {
|
||||||
|
IfcUtil::IfcBaseClass* instance_attribute = *attr;
|
||||||
|
if (instance_attribute == entity) {
|
||||||
|
IfcWrite::IfcWriteArgument* copy = new IfcWrite::IfcWriteArgument();
|
||||||
|
copy->set(boost::blank());
|
||||||
|
related_instance->data().setArgument(i, copy);
|
||||||
|
} }
|
||||||
|
break;
|
||||||
|
case IfcUtil::Argument_AGGREGATE_OF_ENTITY_INSTANCE: {
|
||||||
|
IfcEntityList::ptr instance_list = *attr;
|
||||||
|
if (instance_list->contains(entity)) {
|
||||||
|
instance_list->remove(entity);
|
||||||
|
|
||||||
|
IfcWrite::IfcWriteArgument* copy = new IfcWrite::IfcWriteArgument();
|
||||||
|
copy->set(instance_list);
|
||||||
|
related_instance->data().setArgument(i, copy);
|
||||||
|
} }
|
||||||
|
break;
|
||||||
|
case IfcUtil::Argument_AGGREGATE_OF_AGGREGATE_OF_ENTITY_INSTANCE: {
|
||||||
|
IfcEntityListList::ptr instance_list_list = *attr;
|
||||||
|
if (instance_list_list->contains(entity)) {
|
||||||
|
IfcEntityListList::ptr new_list(new IfcEntityListList);
|
||||||
|
for (IfcEntityListList::outer_it it = instance_list_list->begin(); it != instance_list_list->end(); ++it) {
|
||||||
|
std::vector<IfcUtil::IfcBaseClass*> instances = *it;
|
||||||
|
std::vector<IfcUtil::IfcBaseClass*>::iterator jt;
|
||||||
|
while ((jt = std::find(instances.begin(), instances.end(), entity)) != instances.end()) {
|
||||||
|
instances.erase(jt);
|
||||||
|
}
|
||||||
|
new_list->push(instances);
|
||||||
}
|
}
|
||||||
new_list->push(instances);
|
|
||||||
}
|
|
||||||
|
|
||||||
IfcWrite::IfcWriteArgument* copy = new IfcWrite::IfcWriteArgument();
|
IfcWrite::IfcWriteArgument* copy = new IfcWrite::IfcWriteArgument();
|
||||||
copy->set(new_list);
|
copy->set(new_list);
|
||||||
related_instance->data().setArgument(i, copy);
|
related_instance->data().setArgument(i, copy);
|
||||||
} }
|
} }
|
||||||
break;
|
break;
|
||||||
default: break;
|
default: break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
byref.erase(byref.find(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
IfcEntityList::ptr entity_attributes = traverse(entity, 1);
|
if (!batch_mode_) {
|
||||||
for (IfcEntityList::it it = entity_attributes->begin(); it != entity_attributes->end(); ++it) {
|
byref.erase(id);
|
||||||
IfcUtil::IfcBaseClass* entity_attribute = *it;
|
|
||||||
if (entity_attribute == entity) continue;
|
// This is based on traversal which needs instances to still be contained in the map.
|
||||||
const unsigned int name = entity_attribute->data().id();
|
// another option would be to keep byid intact for the remainder of this loop
|
||||||
// Do not update inverses for simple types (which have id()==0 in IfcOpenShell).
|
IfcEntityList::ptr entity_attributes = traverse(entity, 1);
|
||||||
if (name != 0) {
|
for (IfcEntityList::it it = entity_attributes->begin(); it != entity_attributes->end(); ++it) {
|
||||||
entities_by_ref_t::iterator byref_it = byref.find(name);
|
IfcUtil::IfcBaseClass* entity_attribute = *it;
|
||||||
if (byref_it != byref.end()) {
|
if (entity_attribute == entity) continue;
|
||||||
std::vector<unsigned>& ids = byref_it->second;
|
const unsigned int name = entity_attribute->data().id();
|
||||||
ids.erase(std::remove(ids.begin(), ids.end(), id), ids.end());
|
// Do not update inverses for simple types (which have id()==0 in IfcOpenShell).
|
||||||
|
if (name != 0) {
|
||||||
|
entities_by_ref_t::iterator byref_it = byref.find(name);
|
||||||
|
if (byref_it != byref.end()) {
|
||||||
|
std::vector<unsigned>& ids = byref_it->second;
|
||||||
|
ids.erase(std::remove(ids.begin(), ids.end(), id), ids.end());
|
||||||
|
}
|
||||||
|
by_ref_cached_.erase(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
by_ref_cached_.erase(name);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (entity->declaration().is(*ifcroot_type_)) {
|
if (entity->declaration().is(*ifcroot_type_)) {
|
||||||
const std::string global_id = *entity->data().getArgument(0);
|
const std::string global_id = *entity->data().getArgument(0);
|
||||||
auto it = byguid.find(global_id);
|
auto it = byguid.find(global_id);
|
||||||
if (it != byguid.end()) {
|
if (it != byguid.end()) {
|
||||||
byguid.erase(it);
|
byguid.erase(it);
|
||||||
} else {
|
}
|
||||||
Logger::Warning("GlobalId on rooted instance not encountered in map");
|
else {
|
||||||
|
Logger::Warning("GlobalId on rooted instance not encountered in map");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
byid.erase(byid.find(id));
|
|
||||||
|
|
||||||
const IfcParse::declaration* ty = &entity->declaration();
|
byid.erase(byid.find(id));
|
||||||
|
|
||||||
{
|
const IfcParse::declaration* ty = &entity->declaration();
|
||||||
IfcEntityList::ptr instances_of_same_type = instances_by_type_excl_subtypes(ty);
|
|
||||||
instances_of_same_type->remove(entity);
|
|
||||||
if (instances_of_same_type->size() == 0) {
|
|
||||||
bytype_excl.erase(ty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (;;) {
|
{
|
||||||
IfcEntityList::ptr instances_of_same_type = instances_by_type(ty);
|
IfcEntityList::ptr instances_of_same_type = instances_by_type_excl_subtypes(ty);
|
||||||
if (instances_of_same_type) {
|
|
||||||
instances_of_same_type->remove(entity);
|
instances_of_same_type->remove(entity);
|
||||||
}
|
if (instances_of_same_type->size() == 0) {
|
||||||
if (instances_of_same_type->size() == 0) {
|
bytype_excl.erase(ty);
|
||||||
bytype.erase(ty);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const IfcParse::declaration* pt = ty->as_entity()->supertype();
|
for (;;) {
|
||||||
if (pt) {
|
IfcEntityList::ptr instances_of_same_type = instances_by_type(ty);
|
||||||
ty = pt;
|
if (instances_of_same_type) {
|
||||||
} else {
|
instances_of_same_type->remove(entity);
|
||||||
break;
|
}
|
||||||
}
|
if (instances_of_same_type->size() == 0) {
|
||||||
}
|
bytype.erase(ty);
|
||||||
|
}
|
||||||
|
|
||||||
// This entity_file_map remains obviously flawed, but until we have proper lookup by value, or another mechanism,
|
const IfcParse::declaration* pt = ty->as_entity()->supertype();
|
||||||
// to prevent duplicate definitions with usage of add() we have to keep it. This might be a good moment to clear it.
|
if (pt) {
|
||||||
for (auto it = entity_file_map.begin(); it != entity_file_map.end();) {
|
ty = pt;
|
||||||
if (it->second == entity) {
|
}
|
||||||
it = entity_file_map.erase(it);
|
else {
|
||||||
} else {
|
break;
|
||||||
++it;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This entity_file_map remains obviously flawed, but until we have proper lookup by value, or another mechanism,
|
||||||
|
// to prevent duplicate definitions with usage of add() we have to keep it. This might be a good moment to clear it.
|
||||||
|
for (auto it = entity_file_map.begin(); it != entity_file_map.end();) {
|
||||||
|
if (it->second == entity) {
|
||||||
|
it = entity_file_map.erase(it);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete entity;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete entity;
|
if (batch_mode_) {
|
||||||
|
for (auto it = byref.begin(); it != byref.end();) {
|
||||||
|
bool do_delete = batch_deletion_ids_.find(it->first) != batch_deletion_ids_.end();
|
||||||
|
if (!do_delete) {
|
||||||
|
it->second.erase(std::remove_if(it->second.begin(), it->second.end(), [this](int x) {
|
||||||
|
return batch_deletion_ids_.find(x) != batch_deletion_ids_.end();
|
||||||
|
}), it->second.end());
|
||||||
|
do_delete = it->second.empty();
|
||||||
|
}
|
||||||
|
if (do_delete) {
|
||||||
|
it = byref.erase(it);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
by_ref_cached_.clear();
|
||||||
|
|
||||||
|
batch_deletion_ids_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
IfcEntityList::ptr IfcFile::instances_by_type(const IfcParse::declaration* t) {
|
IfcEntityList::ptr IfcFile::instances_by_type(const IfcParse::declaration* t) {
|
||||||
|
|||||||
Reference in New Issue
Block a user