mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-03 10:22:45 -04:00
better texture filename extension error reporting
This commit is contained in:
parent
7312bb5a0d
commit
942f595c7f
@ -49,9 +49,9 @@
|
|||||||
#include "pgTop.h"
|
#include "pgTop.h"
|
||||||
#include "geomNode.h"
|
#include "geomNode.h"
|
||||||
#include "texture.h"
|
#include "texture.h"
|
||||||
#include "pnmImage.h"
|
#include "videoTexture.h"
|
||||||
|
#include "texturePool.h"
|
||||||
#include "loaderFileTypeRegistry.h"
|
#include "loaderFileTypeRegistry.h"
|
||||||
#include "pnmFileTypeRegistry.h"
|
|
||||||
#include "pnmImage.h"
|
#include "pnmImage.h"
|
||||||
#include "virtualFileSystem.h"
|
#include "virtualFileSystem.h"
|
||||||
|
|
||||||
@ -562,12 +562,10 @@ load_model(const NodePath &parent, Filename filename) {
|
|||||||
reg->get_type_from_extension(extension);
|
reg->get_type_from_extension(extension);
|
||||||
if (model_type == (LoaderFileType *)NULL) {
|
if (model_type == (LoaderFileType *)NULL) {
|
||||||
// The extension isn't a known model file type, is it a known
|
// The extension isn't a known model file type, is it a known
|
||||||
// image extension?
|
// texture extension?
|
||||||
PNMFileTypeRegistry *reg = PNMFileTypeRegistry::get_global_ptr();
|
TexturePool *texture_pool = TexturePool::get_global_ptr();
|
||||||
PNMFileType *image_type =
|
if (texture_pool->get_texture_type(extension) != NULL) {
|
||||||
reg->get_type_from_extension(extension);
|
// It is a known texture extension.
|
||||||
if (image_type != (PNMFileType *)NULL) {
|
|
||||||
// It is a known image extension.
|
|
||||||
is_image = true;
|
is_image = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -981,20 +979,35 @@ setup_lights() {
|
|||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
PT(PandaNode) WindowFramework::
|
PT(PandaNode) WindowFramework::
|
||||||
load_image_as_model(const Filename &filename) {
|
load_image_as_model(const Filename &filename) {
|
||||||
PNMImageHeader header;
|
PT(Texture) tex = TexturePool::load_texture(filename);
|
||||||
if (!header.read_header(filename)) {
|
if (tex == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
int x_size = header.get_x_size();
|
int x_size = tex->get_x_size();
|
||||||
int y_size = header.get_y_size();
|
int y_size = tex->get_y_size();
|
||||||
bool has_alpha = header.has_alpha();
|
bool has_alpha = false;
|
||||||
|
LVecBase2f tex_scale(1.0f, 1.0f);
|
||||||
|
|
||||||
|
if (tex->is_of_type(VideoTexture::get_class_type())) {
|
||||||
|
// Get the size from the video stream.
|
||||||
|
VideoTexture *vtex = DCAST(VideoTexture, tex);
|
||||||
|
x_size = vtex->get_video_width();
|
||||||
|
y_size = vtex->get_video_height();
|
||||||
|
tex_scale = vtex->get_tex_scale();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Get the size from the original image (the texture may have
|
||||||
|
// scaled it to make a power of 2).
|
||||||
|
PNMImageHeader header;
|
||||||
|
if (header.read_header(filename)) {
|
||||||
|
x_size = header.get_x_size();
|
||||||
|
y_size = header.get_y_size();
|
||||||
|
has_alpha = header.has_alpha();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Yes, it is an image file; make a texture out of it.
|
// Yes, it is an image file; make a texture out of it.
|
||||||
PT(Texture) tex = new Texture;
|
|
||||||
if (!tex->read(filename)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
tex->set_minfilter(Texture::FT_linear_mipmap_linear);
|
tex->set_minfilter(Texture::FT_linear_mipmap_linear);
|
||||||
tex->set_magfilter(Texture::FT_linear);
|
tex->set_magfilter(Texture::FT_linear);
|
||||||
|
|
||||||
@ -1024,10 +1037,10 @@ load_image_as_model(const Filename &filename) {
|
|||||||
vertex.add_data3f(Vertexf::rfu(right, 0.02f, top));
|
vertex.add_data3f(Vertexf::rfu(right, 0.02f, top));
|
||||||
vertex.add_data3f(Vertexf::rfu(right, 0.02f, bottom));
|
vertex.add_data3f(Vertexf::rfu(right, 0.02f, bottom));
|
||||||
|
|
||||||
texcoord.add_data2f(0.0f, 1.0f);
|
texcoord.add_data2f(0.0f, tex_scale[1]);
|
||||||
texcoord.add_data2f(0.0f, 0.0f);
|
texcoord.add_data2f(0.0f, 0.0f);
|
||||||
texcoord.add_data2f(1.0f, 1.0f);
|
texcoord.add_data2f(tex_scale[0], tex_scale[1]);
|
||||||
texcoord.add_data2f(1.0f, 0.0f);
|
texcoord.add_data2f(tex_scale[0], 0.0f);
|
||||||
|
|
||||||
PT(GeomTristrips) strip = new GeomTristrips(Geom::UH_static);
|
PT(GeomTristrips) strip = new GeomTristrips(Geom::UH_static);
|
||||||
strip->add_consecutive_vertices(0, 4);
|
strip->add_consecutive_vertices(0, 4);
|
||||||
|
@ -289,7 +289,7 @@ bool Texture::
|
|||||||
read(const Filename &fullpath, int z, int primary_file_num_channels) {
|
read(const Filename &fullpath, int z, int primary_file_num_channels) {
|
||||||
PNMImage image;
|
PNMImage image;
|
||||||
|
|
||||||
if (!image.read(fullpath)) {
|
if (!image.read(fullpath, NULL, false)) {
|
||||||
gobj_cat.error()
|
gobj_cat.error()
|
||||||
<< "Texture::read() - couldn't read: " << fullpath << endl;
|
<< "Texture::read() - couldn't read: " << fullpath << endl;
|
||||||
return false;
|
return false;
|
||||||
@ -328,14 +328,14 @@ bool Texture::
|
|||||||
read(const Filename &fullpath, const Filename &alpha_fullpath,
|
read(const Filename &fullpath, const Filename &alpha_fullpath,
|
||||||
int z, int primary_file_num_channels, int alpha_file_channel) {
|
int z, int primary_file_num_channels, int alpha_file_channel) {
|
||||||
PNMImage image;
|
PNMImage image;
|
||||||
if (!image.read(fullpath)) {
|
if (!image.read(fullpath, NULL, false)) {
|
||||||
gobj_cat.error()
|
gobj_cat.error()
|
||||||
<< "Texture::read() - couldn't read: " << fullpath << endl;
|
<< "Texture::read() - couldn't read: " << fullpath << endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
PNMImage alpha_image;
|
PNMImage alpha_image;
|
||||||
if (!alpha_image.read(alpha_fullpath)) {
|
if (!alpha_image.read(alpha_fullpath, NULL, true)) {
|
||||||
gobj_cat.error()
|
gobj_cat.error()
|
||||||
<< "Texture::read() - couldn't read (alpha): " << alpha_fullpath << endl;
|
<< "Texture::read() - couldn't read (alpha): " << alpha_fullpath << endl;
|
||||||
return false;
|
return false;
|
||||||
@ -1330,6 +1330,17 @@ string_filter_type(const string &string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: Texture::make_texture
|
||||||
|
// Access: Public, Static
|
||||||
|
// Description: A factory function to make a new Texture, used to
|
||||||
|
// pass to the TexturePool.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
PT(Texture) Texture::
|
||||||
|
make_texture() {
|
||||||
|
return new Texture;
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
// Function: Texture::reconsider_dirty
|
// Function: Texture::reconsider_dirty
|
||||||
// Access: Protected, Virtual
|
// Access: Protected, Virtual
|
||||||
|
@ -277,6 +277,8 @@ public:
|
|||||||
static WrapMode string_wrap_mode(const string &string);
|
static WrapMode string_wrap_mode(const string &string);
|
||||||
static FilterType string_filter_type(const string &string);
|
static FilterType string_filter_type(const string &string);
|
||||||
|
|
||||||
|
static PT(Texture) make_texture();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void reconsider_dirty();
|
virtual void reconsider_dirty();
|
||||||
virtual void reload_ram_image();
|
virtual void reload_ram_image();
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include "config_express.h"
|
#include "config_express.h"
|
||||||
#include "string_utils.h"
|
#include "string_utils.h"
|
||||||
#include "virtualFileSystem.h"
|
#include "virtualFileSystem.h"
|
||||||
|
#include "pnmFileTypeRegistry.h"
|
||||||
|
|
||||||
|
|
||||||
TexturePool *TexturePool::_global_ptr = (TexturePool *)NULL;
|
TexturePool *TexturePool::_global_ptr = (TexturePool *)NULL;
|
||||||
@ -59,6 +60,36 @@ register_texture_type(MakeTextureFunc *func, const string &extensions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: TexturePool::get_texture_type
|
||||||
|
// Access: Public
|
||||||
|
// Description: Returns the factory function to construct a new
|
||||||
|
// texture of the type appropriate for the indicated
|
||||||
|
// filename extension, if any, or NULL if the extension
|
||||||
|
// is not one of the extensions for a texture file.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
TexturePool::MakeTextureFunc *TexturePool::
|
||||||
|
get_texture_type(const string &extension) const {
|
||||||
|
string c = downcase(extension);
|
||||||
|
TypeRegistry::const_iterator ti;
|
||||||
|
ti = _type_registry.find(extension);
|
||||||
|
if (ti != _type_registry.end()) {
|
||||||
|
return (*ti).second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the PNM type registry.
|
||||||
|
PNMFileTypeRegistry *pnm_reg = PNMFileTypeRegistry::get_global_ptr();
|
||||||
|
PNMFileType *type = pnm_reg->get_type_from_extension(extension);
|
||||||
|
if (type != (PNMFileType *)NULL) {
|
||||||
|
// This is a known image type; create an ordinary Texture.
|
||||||
|
((TexturePool *)this)->_type_registry[extension] = Texture::make_texture;
|
||||||
|
return Texture::make_texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is an unknown texture type.
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
// Function: TexturePool::make_texture
|
// Function: TexturePool::make_texture
|
||||||
// Access: Public
|
// Access: Public
|
||||||
@ -68,16 +99,47 @@ register_texture_type(MakeTextureFunc *func, const string &extensions) {
|
|||||||
// register_texture_type().
|
// register_texture_type().
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
PT(Texture) TexturePool::
|
PT(Texture) TexturePool::
|
||||||
make_texture(const string &extension) {
|
make_texture(const string &extension) const {
|
||||||
string c = downcase(extension);
|
MakeTextureFunc *func = get_texture_type(extension);
|
||||||
TypeRegistry::const_iterator ti;
|
if (func != NULL) {
|
||||||
ti = _type_registry.find(extension);
|
return func();
|
||||||
if (ti != _type_registry.end()) {
|
|
||||||
return (*ti).second();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We don't know what kind of file type this is; return an ordinary
|
||||||
|
// Texture in case it's an image file with no extension.
|
||||||
return new Texture;
|
return new Texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
// Function: TexturePool::write_texture_types
|
||||||
|
// Access: Public
|
||||||
|
// Description: Outputs a list of the available texture types to the
|
||||||
|
// indicated output stream. This is mostly the list of
|
||||||
|
// available image types, with maybe a few additional
|
||||||
|
// ones for video textures.
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
void TexturePool::
|
||||||
|
write_texture_types(ostream &out, int indent_level) const {
|
||||||
|
PNMFileTypeRegistry *pnm_reg = PNMFileTypeRegistry::get_global_ptr();
|
||||||
|
pnm_reg->write(out, indent_level);
|
||||||
|
|
||||||
|
// Also output any of the additional texture types, that aren't
|
||||||
|
// strictly images (these are typically video textures).
|
||||||
|
TypeRegistry::const_iterator ti;
|
||||||
|
for (ti = _type_registry.begin(); ti != _type_registry.end(); ++ti) {
|
||||||
|
string extension = (*ti).first;
|
||||||
|
MakeTextureFunc *func = (*ti).second;
|
||||||
|
|
||||||
|
if (pnm_reg->get_type_from_extension(extension) == NULL) {
|
||||||
|
PT(Texture) tex = func();
|
||||||
|
string name = tex->get_type().get_name();
|
||||||
|
indent(out, indent_level) << name;
|
||||||
|
indent(out, max(30 - (int)name.length(), 0))
|
||||||
|
<< " ." << extension << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
// Function: TexturePool::get_global_ptr
|
// Function: TexturePool::get_global_ptr
|
||||||
// Access: Public, Static
|
// Access: Public, Static
|
||||||
@ -463,5 +525,14 @@ report_texture_unreadable(const Filename &filename) const {
|
|||||||
// The file exists, but it couldn't be read for some reason.
|
// The file exists, but it couldn't be read for some reason.
|
||||||
gobj_cat.error()
|
gobj_cat.error()
|
||||||
<< "Texture \"" << filename << "\" exists but cannot be read.\n";
|
<< "Texture \"" << filename << "\" exists but cannot be read.\n";
|
||||||
|
|
||||||
|
// Maybe the filename extension is unknown.
|
||||||
|
MakeTextureFunc *func = get_texture_type(filename.get_extension());
|
||||||
|
if (func == (MakeTextureFunc *)NULL) {
|
||||||
|
gobj_cat.error()
|
||||||
|
<< "Texture extension \"" << filename.get_extension()
|
||||||
|
<< "\" is unknown. Supported texture types:\n";
|
||||||
|
write_texture_types(gobj_cat.error(false), 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,9 @@ public:
|
|||||||
typedef PT(Texture) MakeTextureFunc();
|
typedef PT(Texture) MakeTextureFunc();
|
||||||
void register_texture_type(MakeTextureFunc *func, const string &extensions);
|
void register_texture_type(MakeTextureFunc *func, const string &extensions);
|
||||||
|
|
||||||
PT(Texture) make_texture(const string &extension);
|
MakeTextureFunc *get_texture_type(const string &extension) const;
|
||||||
|
PT(Texture) make_texture(const string &extension) const;
|
||||||
|
void write_texture_types(ostream &out, int indent_level) const;
|
||||||
|
|
||||||
static TexturePool *get_global_ptr();
|
static TexturePool *get_global_ptr();
|
||||||
|
|
||||||
|
@ -158,10 +158,11 @@ alpha_fill_val(xelval alpha) {
|
|||||||
// is. Returns true if successful, false on error.
|
// is. Returns true if successful, false on error.
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
bool PNMImage::
|
bool PNMImage::
|
||||||
read(const Filename &filename, PNMFileType *type) {
|
read(const Filename &filename, PNMFileType *type, bool report_unknown_type) {
|
||||||
clear();
|
clear();
|
||||||
|
|
||||||
PNMReader *reader = PNMImageHeader::make_reader(filename, type);
|
PNMReader *reader = PNMImageHeader::make_reader(filename, type,
|
||||||
|
report_unknown_type);
|
||||||
if (reader == (PNMReader *)NULL) {
|
if (reader == (PNMReader *)NULL) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -174,18 +175,21 @@ read(const Filename &filename, PNMFileType *type) {
|
|||||||
// Description: Reads the image data from the indicated stream.
|
// Description: Reads the image data from the indicated stream.
|
||||||
//
|
//
|
||||||
// The filename is advisory only, and may be used
|
// The filename is advisory only, and may be used
|
||||||
// suggest a type if it has a known extension.
|
// to suggest a type if it has a known extension.
|
||||||
//
|
//
|
||||||
// If type is non-NULL, it is a suggestion for the type
|
// If type is non-NULL, it is a suggestion for the type
|
||||||
// of file it is. Returns true if successful, false on
|
// of file it is (and a non-NULL type will override any
|
||||||
// error.
|
// magic number test or filename extension lookup).
|
||||||
|
//
|
||||||
|
// Returns true if successful, false on error.
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
bool PNMImage::
|
bool PNMImage::
|
||||||
read(istream &data, const string &filename, PNMFileType *type) {
|
read(istream &data, const string &filename, PNMFileType *type,
|
||||||
|
bool report_unknown_type) {
|
||||||
clear();
|
clear();
|
||||||
|
|
||||||
PNMReader *reader = PNMImageHeader::make_reader
|
PNMReader *reader = PNMImageHeader::make_reader
|
||||||
(&data, false, filename, string(), type);
|
(&data, false, filename, string(), type, report_unknown_type);
|
||||||
if (reader == (PNMReader *)NULL) {
|
if (reader == (PNMReader *)NULL) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -87,9 +87,11 @@ PUBLISHED:
|
|||||||
INLINE void alpha_fill(double alpha = 0.0);
|
INLINE void alpha_fill(double alpha = 0.0);
|
||||||
void alpha_fill_val(xelval alpha = 0);
|
void alpha_fill_val(xelval alpha = 0);
|
||||||
|
|
||||||
bool read(const Filename &filename, PNMFileType *type = NULL);
|
bool read(const Filename &filename, PNMFileType *type = NULL,
|
||||||
|
bool report_unknown_type = true);
|
||||||
bool read(istream &data, const string &filename = string(),
|
bool read(istream &data, const string &filename = string(),
|
||||||
PNMFileType *type = NULL);
|
PNMFileType *type = NULL,
|
||||||
|
bool report_unknown_type = true);
|
||||||
bool read(PNMReader *reader);
|
bool read(PNMReader *reader);
|
||||||
|
|
||||||
bool write(const Filename &filename, PNMFileType *type = NULL) const;
|
bool write(const Filename &filename, PNMFileType *type = NULL) const;
|
||||||
|
@ -58,7 +58,8 @@ read_header(const Filename &filename, PNMFileType *type) {
|
|||||||
// needed.
|
// needed.
|
||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
PNMReader *PNMImageHeader::
|
PNMReader *PNMImageHeader::
|
||||||
make_reader(const Filename &filename, PNMFileType *type) const {
|
make_reader(const Filename &filename, PNMFileType *type,
|
||||||
|
bool report_unknown_type) const {
|
||||||
if (pnmimage_cat.is_debug()) {
|
if (pnmimage_cat.is_debug()) {
|
||||||
pnmimage_cat.debug()
|
pnmimage_cat.debug()
|
||||||
<< "Reading image from " << filename << "\n";
|
<< "Reading image from " << filename << "\n";
|
||||||
@ -88,7 +89,8 @@ make_reader(const Filename &filename, PNMFileType *type) const {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return make_reader(file, owns_file, filename, string(), type);
|
return make_reader(file, owns_file, filename, string(), type,
|
||||||
|
report_unknown_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -123,7 +125,8 @@ make_reader(const Filename &filename, PNMFileType *type) const {
|
|||||||
////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////
|
||||||
PNMReader *PNMImageHeader::
|
PNMReader *PNMImageHeader::
|
||||||
make_reader(istream *file, bool owns_file, const Filename &filename,
|
make_reader(istream *file, bool owns_file, const Filename &filename,
|
||||||
string magic_number, PNMFileType *type) const {
|
string magic_number, PNMFileType *type,
|
||||||
|
bool report_unknown_type) const {
|
||||||
if (type == (PNMFileType *)NULL) {
|
if (type == (PNMFileType *)NULL) {
|
||||||
if (!read_magic_number(file, magic_number, 2)) {
|
if (!read_magic_number(file, magic_number, 2)) {
|
||||||
// No magic number. No image.
|
// No magic number. No image.
|
||||||
@ -188,7 +191,7 @@ make_reader(istream *file, bool owns_file, const Filename &filename,
|
|||||||
|
|
||||||
if (type == (PNMFileType *)NULL) {
|
if (type == (PNMFileType *)NULL) {
|
||||||
// We can't figure out what type the file is; give up.
|
// We can't figure out what type the file is; give up.
|
||||||
if (pnmimage_cat.is_error()) {
|
if (report_unknown_type && pnmimage_cat.is_error()) {
|
||||||
pnmimage_cat.error()
|
pnmimage_cat.error()
|
||||||
<< "Cannot determine type of image file " << filename << ".\n"
|
<< "Cannot determine type of image file " << filename << ".\n"
|
||||||
<< "Currently supported image types:\n";
|
<< "Currently supported image types:\n";
|
||||||
|
@ -79,11 +79,13 @@ PUBLISHED:
|
|||||||
bool read_header(const Filename &filename, PNMFileType *type = NULL);
|
bool read_header(const Filename &filename, PNMFileType *type = NULL);
|
||||||
|
|
||||||
PNMReader *make_reader(const Filename &filename,
|
PNMReader *make_reader(const Filename &filename,
|
||||||
PNMFileType *type = NULL) const;
|
PNMFileType *type = NULL,
|
||||||
|
bool report_unknown_type = true) const;
|
||||||
PNMReader *make_reader(istream *file, bool owns_file = true,
|
PNMReader *make_reader(istream *file, bool owns_file = true,
|
||||||
const Filename &filename = Filename(),
|
const Filename &filename = Filename(),
|
||||||
string magic_number = string(),
|
string magic_number = string(),
|
||||||
PNMFileType *type = NULL) const;
|
PNMFileType *type = NULL,
|
||||||
|
bool report_unknown_type = true) const;
|
||||||
|
|
||||||
PNMWriter *make_writer(const Filename &filename,
|
PNMWriter *make_writer(const Filename &filename,
|
||||||
PNMFileType *type = NULL) const;
|
PNMFileType *type = NULL) const;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user