src: rename StreamWrap to LibuvStreamWrap
This should help clarify what kind of resource a `StreamWrap` represents. PR-URL: https://github.com/nodejs/node/pull/16157 Reviewed-By: Daniel Bevenius <daniel.bevenius@gmail.com> Reviewed-By: Daijiro Wachi <daijiro.wachi@gmail.com> Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Timothy Gu <timothygu99@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
This commit is contained in:
parent
7245e852f2
commit
00fe76d2df
@ -24,10 +24,10 @@ template <typename WrapType, typename UVType>
|
|||||||
ConnectionWrap<WrapType, UVType>::ConnectionWrap(Environment* env,
|
ConnectionWrap<WrapType, UVType>::ConnectionWrap(Environment* env,
|
||||||
Local<Object> object,
|
Local<Object> object,
|
||||||
ProviderType provider)
|
ProviderType provider)
|
||||||
: StreamWrap(env,
|
: LibuvStreamWrap(env,
|
||||||
object,
|
object,
|
||||||
reinterpret_cast<uv_stream_t*>(&handle_),
|
reinterpret_cast<uv_stream_t*>(&handle_),
|
||||||
provider) {}
|
provider) {}
|
||||||
|
|
||||||
|
|
||||||
template <typename WrapType, typename UVType>
|
template <typename WrapType, typename UVType>
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
namespace node {
|
namespace node {
|
||||||
|
|
||||||
template <typename WrapType, typename UVType>
|
template <typename WrapType, typename UVType>
|
||||||
class ConnectionWrap : public StreamWrap {
|
class ConnectionWrap : public LibuvStreamWrap {
|
||||||
public:
|
public:
|
||||||
UVType* UVHandle() {
|
UVType* UVHandle() {
|
||||||
return &handle_;
|
return &handle_;
|
||||||
|
@ -80,9 +80,9 @@ void PipeWrap::Initialize(Local<Object> target,
|
|||||||
env->SetProtoMethod(t, "hasRef", HandleWrap::HasRef);
|
env->SetProtoMethod(t, "hasRef", HandleWrap::HasRef);
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
StreamWrap::AddMethods(env, t);
|
LibuvStreamWrap::AddMethods(env, t);
|
||||||
#else
|
#else
|
||||||
StreamWrap::AddMethods(env, t, StreamBase::kFlagHasWritev);
|
LibuvStreamWrap::AddMethods(env, t, StreamBase::kFlagHasWritev);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
env->SetProtoMethod(t, "bind", Bind);
|
env->SetProtoMethod(t, "bind", Bind);
|
||||||
|
@ -365,7 +365,7 @@ int StreamBase::WriteString(const FunctionCallbackInfo<Value>& args) {
|
|||||||
HandleWrap* wrap;
|
HandleWrap* wrap;
|
||||||
ASSIGN_OR_RETURN_UNWRAP(&wrap, send_handle_obj, UV_EINVAL);
|
ASSIGN_OR_RETURN_UNWRAP(&wrap, send_handle_obj, UV_EINVAL);
|
||||||
send_handle = wrap->GetHandle();
|
send_handle = wrap->GetHandle();
|
||||||
// Reference StreamWrap instance to prevent it from being garbage
|
// Reference LibuvStreamWrap instance to prevent it from being garbage
|
||||||
// collected before `AfterWrite` is called.
|
// collected before `AfterWrite` is called.
|
||||||
CHECK_EQ(false, req_wrap->persistent().IsEmpty());
|
CHECK_EQ(false, req_wrap->persistent().IsEmpty());
|
||||||
req_wrap_obj->Set(env->handle_string(), send_handle_obj);
|
req_wrap_obj->Set(env->handle_string(), send_handle_obj);
|
||||||
|
@ -54,9 +54,9 @@ using v8::Object;
|
|||||||
using v8::Value;
|
using v8::Value;
|
||||||
|
|
||||||
|
|
||||||
void StreamWrap::Initialize(Local<Object> target,
|
void LibuvStreamWrap::Initialize(Local<Object> target,
|
||||||
Local<Value> unused,
|
Local<Value> unused,
|
||||||
Local<Context> context) {
|
Local<Context> context) {
|
||||||
Environment* env = Environment::GetCurrent(context);
|
Environment* env = Environment::GetCurrent(context);
|
||||||
|
|
||||||
auto is_construct_call_callback =
|
auto is_construct_call_callback =
|
||||||
@ -85,10 +85,10 @@ void StreamWrap::Initialize(Local<Object> target,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
StreamWrap::StreamWrap(Environment* env,
|
LibuvStreamWrap::LibuvStreamWrap(Environment* env,
|
||||||
Local<Object> object,
|
Local<Object> object,
|
||||||
uv_stream_t* stream,
|
uv_stream_t* stream,
|
||||||
AsyncWrap::ProviderType provider)
|
AsyncWrap::ProviderType provider)
|
||||||
: HandleWrap(env,
|
: HandleWrap(env,
|
||||||
object,
|
object,
|
||||||
reinterpret_cast<uv_handle_t*>(stream),
|
reinterpret_cast<uv_handle_t*>(stream),
|
||||||
@ -101,15 +101,15 @@ StreamWrap::StreamWrap(Environment* env,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void StreamWrap::AddMethods(Environment* env,
|
void LibuvStreamWrap::AddMethods(Environment* env,
|
||||||
v8::Local<v8::FunctionTemplate> target,
|
v8::Local<v8::FunctionTemplate> target,
|
||||||
int flags) {
|
int flags) {
|
||||||
env->SetProtoMethod(target, "setBlocking", SetBlocking);
|
env->SetProtoMethod(target, "setBlocking", SetBlocking);
|
||||||
StreamBase::AddMethods<StreamWrap>(env, target, flags);
|
StreamBase::AddMethods<LibuvStreamWrap>(env, target, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int StreamWrap::GetFD() {
|
int LibuvStreamWrap::GetFD() {
|
||||||
int fd = -1;
|
int fd = -1;
|
||||||
#if !defined(_WIN32)
|
#if !defined(_WIN32)
|
||||||
if (stream() != nullptr)
|
if (stream() != nullptr)
|
||||||
@ -119,32 +119,32 @@ int StreamWrap::GetFD() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool StreamWrap::IsAlive() {
|
bool LibuvStreamWrap::IsAlive() {
|
||||||
return HandleWrap::IsAlive(this);
|
return HandleWrap::IsAlive(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool StreamWrap::IsClosing() {
|
bool LibuvStreamWrap::IsClosing() {
|
||||||
return uv_is_closing(reinterpret_cast<uv_handle_t*>(stream()));
|
return uv_is_closing(reinterpret_cast<uv_handle_t*>(stream()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void* StreamWrap::Cast() {
|
void* LibuvStreamWrap::Cast() {
|
||||||
return reinterpret_cast<void*>(this);
|
return reinterpret_cast<void*>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
AsyncWrap* StreamWrap::GetAsyncWrap() {
|
AsyncWrap* LibuvStreamWrap::GetAsyncWrap() {
|
||||||
return static_cast<AsyncWrap*>(this);
|
return static_cast<AsyncWrap*>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool StreamWrap::IsIPCPipe() {
|
bool LibuvStreamWrap::IsIPCPipe() {
|
||||||
return is_named_pipe_ipc();
|
return is_named_pipe_ipc();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void StreamWrap::UpdateWriteQueueSize() {
|
void LibuvStreamWrap::UpdateWriteQueueSize() {
|
||||||
HandleScope scope(env()->isolate());
|
HandleScope scope(env()->isolate());
|
||||||
Local<Integer> write_queue_size =
|
Local<Integer> write_queue_size =
|
||||||
Integer::NewFromUnsigned(env()->isolate(), stream()->write_queue_size);
|
Integer::NewFromUnsigned(env()->isolate(), stream()->write_queue_size);
|
||||||
@ -152,20 +152,20 @@ void StreamWrap::UpdateWriteQueueSize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int StreamWrap::ReadStart() {
|
int LibuvStreamWrap::ReadStart() {
|
||||||
return uv_read_start(stream(), OnAlloc, OnRead);
|
return uv_read_start(stream(), OnAlloc, OnRead);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int StreamWrap::ReadStop() {
|
int LibuvStreamWrap::ReadStop() {
|
||||||
return uv_read_stop(stream());
|
return uv_read_stop(stream());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void StreamWrap::OnAlloc(uv_handle_t* handle,
|
void LibuvStreamWrap::OnAlloc(uv_handle_t* handle,
|
||||||
size_t suggested_size,
|
size_t suggested_size,
|
||||||
uv_buf_t* buf) {
|
uv_buf_t* buf) {
|
||||||
StreamWrap* wrap = static_cast<StreamWrap*>(handle->data);
|
LibuvStreamWrap* wrap = static_cast<LibuvStreamWrap*>(handle->data);
|
||||||
HandleScope scope(wrap->env()->isolate());
|
HandleScope scope(wrap->env()->isolate());
|
||||||
Context::Scope context_scope(wrap->env()->context());
|
Context::Scope context_scope(wrap->env()->context());
|
||||||
|
|
||||||
@ -175,14 +175,14 @@ void StreamWrap::OnAlloc(uv_handle_t* handle,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void StreamWrap::OnAllocImpl(size_t size, uv_buf_t* buf, void* ctx) {
|
void LibuvStreamWrap::OnAllocImpl(size_t size, uv_buf_t* buf, void* ctx) {
|
||||||
buf->base = node::Malloc(size);
|
buf->base = node::Malloc(size);
|
||||||
buf->len = size;
|
buf->len = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
template <class WrapType, class UVType>
|
template <class WrapType, class UVType>
|
||||||
static Local<Object> AcceptHandle(Environment* env, StreamWrap* parent) {
|
static Local<Object> AcceptHandle(Environment* env, LibuvStreamWrap* parent) {
|
||||||
EscapableHandleScope scope(env->isolate());
|
EscapableHandleScope scope(env->isolate());
|
||||||
Local<Object> wrap_obj;
|
Local<Object> wrap_obj;
|
||||||
UVType* handle;
|
UVType* handle;
|
||||||
@ -202,11 +202,11 @@ static Local<Object> AcceptHandle(Environment* env, StreamWrap* parent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void StreamWrap::OnReadImpl(ssize_t nread,
|
void LibuvStreamWrap::OnReadImpl(ssize_t nread,
|
||||||
const uv_buf_t* buf,
|
const uv_buf_t* buf,
|
||||||
uv_handle_type pending,
|
uv_handle_type pending,
|
||||||
void* ctx) {
|
void* ctx) {
|
||||||
StreamWrap* wrap = static_cast<StreamWrap*>(ctx);
|
LibuvStreamWrap* wrap = static_cast<LibuvStreamWrap*>(ctx);
|
||||||
Environment* env = wrap->env();
|
Environment* env = wrap->env();
|
||||||
HandleScope handle_scope(env->isolate());
|
HandleScope handle_scope(env->isolate());
|
||||||
Context::Scope context_scope(env->context());
|
Context::Scope context_scope(env->context());
|
||||||
@ -244,10 +244,10 @@ void StreamWrap::OnReadImpl(ssize_t nread,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void StreamWrap::OnRead(uv_stream_t* handle,
|
void LibuvStreamWrap::OnRead(uv_stream_t* handle,
|
||||||
ssize_t nread,
|
ssize_t nread,
|
||||||
const uv_buf_t* buf) {
|
const uv_buf_t* buf) {
|
||||||
StreamWrap* wrap = static_cast<StreamWrap*>(handle->data);
|
LibuvStreamWrap* wrap = static_cast<LibuvStreamWrap*>(handle->data);
|
||||||
HandleScope scope(wrap->env()->isolate());
|
HandleScope scope(wrap->env()->isolate());
|
||||||
Context::Scope context_scope(wrap->env()->context());
|
Context::Scope context_scope(wrap->env()->context());
|
||||||
uv_handle_type type = UV_UNKNOWN_HANDLE;
|
uv_handle_type type = UV_UNKNOWN_HANDLE;
|
||||||
@ -273,8 +273,8 @@ void StreamWrap::OnRead(uv_stream_t* handle,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void StreamWrap::SetBlocking(const FunctionCallbackInfo<Value>& args) {
|
void LibuvStreamWrap::SetBlocking(const FunctionCallbackInfo<Value>& args) {
|
||||||
StreamWrap* wrap;
|
LibuvStreamWrap* wrap;
|
||||||
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
|
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
|
||||||
|
|
||||||
CHECK_GT(args.Length(), 0);
|
CHECK_GT(args.Length(), 0);
|
||||||
@ -286,7 +286,7 @@ void StreamWrap::SetBlocking(const FunctionCallbackInfo<Value>& args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int StreamWrap::DoShutdown(ShutdownWrap* req_wrap) {
|
int LibuvStreamWrap::DoShutdown(ShutdownWrap* req_wrap) {
|
||||||
int err;
|
int err;
|
||||||
err = uv_shutdown(req_wrap->req(), stream(), AfterShutdown);
|
err = uv_shutdown(req_wrap->req(), stream(), AfterShutdown);
|
||||||
req_wrap->Dispatched();
|
req_wrap->Dispatched();
|
||||||
@ -294,7 +294,7 @@ int StreamWrap::DoShutdown(ShutdownWrap* req_wrap) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void StreamWrap::AfterShutdown(uv_shutdown_t* req, int status) {
|
void LibuvStreamWrap::AfterShutdown(uv_shutdown_t* req, int status) {
|
||||||
ShutdownWrap* req_wrap = ShutdownWrap::from_req(req);
|
ShutdownWrap* req_wrap = ShutdownWrap::from_req(req);
|
||||||
CHECK_NE(req_wrap, nullptr);
|
CHECK_NE(req_wrap, nullptr);
|
||||||
HandleScope scope(req_wrap->env()->isolate());
|
HandleScope scope(req_wrap->env()->isolate());
|
||||||
@ -307,7 +307,7 @@ void StreamWrap::AfterShutdown(uv_shutdown_t* req, int status) {
|
|||||||
// values, shifting their base and decrementing their length. This is
|
// values, shifting their base and decrementing their length. This is
|
||||||
// required in order to skip the data that was successfully written via
|
// required in order to skip the data that was successfully written via
|
||||||
// uv_try_write().
|
// uv_try_write().
|
||||||
int StreamWrap::DoTryWrite(uv_buf_t** bufs, size_t* count) {
|
int LibuvStreamWrap::DoTryWrite(uv_buf_t** bufs, size_t* count) {
|
||||||
int err;
|
int err;
|
||||||
size_t written;
|
size_t written;
|
||||||
uv_buf_t* vbufs = *bufs;
|
uv_buf_t* vbufs = *bufs;
|
||||||
@ -343,7 +343,7 @@ int StreamWrap::DoTryWrite(uv_buf_t** bufs, size_t* count) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int StreamWrap::DoWrite(WriteWrap* w,
|
int LibuvStreamWrap::DoWrite(WriteWrap* w,
|
||||||
uv_buf_t* bufs,
|
uv_buf_t* bufs,
|
||||||
size_t count,
|
size_t count,
|
||||||
uv_stream_t* send_handle) {
|
uv_stream_t* send_handle) {
|
||||||
@ -372,7 +372,7 @@ int StreamWrap::DoWrite(WriteWrap* w,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void StreamWrap::AfterWrite(uv_write_t* req, int status) {
|
void LibuvStreamWrap::AfterWrite(uv_write_t* req, int status) {
|
||||||
WriteWrap* req_wrap = WriteWrap::from_req(req);
|
WriteWrap* req_wrap = WriteWrap::from_req(req);
|
||||||
CHECK_NE(req_wrap, nullptr);
|
CHECK_NE(req_wrap, nullptr);
|
||||||
HandleScope scope(req_wrap->env()->isolate());
|
HandleScope scope(req_wrap->env()->isolate());
|
||||||
@ -381,11 +381,12 @@ void StreamWrap::AfterWrite(uv_write_t* req, int status) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void StreamWrap::OnAfterWriteImpl(WriteWrap* w, void* ctx) {
|
void LibuvStreamWrap::OnAfterWriteImpl(WriteWrap* w, void* ctx) {
|
||||||
StreamWrap* wrap = static_cast<StreamWrap*>(ctx);
|
LibuvStreamWrap* wrap = static_cast<LibuvStreamWrap*>(ctx);
|
||||||
wrap->UpdateWriteQueueSize();
|
wrap->UpdateWriteQueueSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
|
||||||
NODE_MODULE_CONTEXT_AWARE_BUILTIN(stream_wrap, node::StreamWrap::Initialize)
|
NODE_MODULE_CONTEXT_AWARE_BUILTIN(stream_wrap,
|
||||||
|
node::LibuvStreamWrap::Initialize)
|
||||||
|
@ -33,10 +33,7 @@
|
|||||||
|
|
||||||
namespace node {
|
namespace node {
|
||||||
|
|
||||||
// Forward declaration
|
class LibuvStreamWrap : public HandleWrap, public StreamBase {
|
||||||
class StreamWrap;
|
|
||||||
|
|
||||||
class StreamWrap : public HandleWrap, public StreamBase {
|
|
||||||
public:
|
public:
|
||||||
static void Initialize(v8::Local<v8::Object> target,
|
static void Initialize(v8::Local<v8::Object> target,
|
||||||
v8::Local<v8::Value> unused,
|
v8::Local<v8::Value> unused,
|
||||||
@ -78,12 +75,12 @@ class StreamWrap : public HandleWrap, public StreamBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
StreamWrap(Environment* env,
|
LibuvStreamWrap(Environment* env,
|
||||||
v8::Local<v8::Object> object,
|
v8::Local<v8::Object> object,
|
||||||
uv_stream_t* stream,
|
uv_stream_t* stream,
|
||||||
AsyncWrap::ProviderType provider);
|
AsyncWrap::ProviderType provider);
|
||||||
|
|
||||||
~StreamWrap() {
|
~LibuvStreamWrap() {
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWrap* GetAsyncWrap() override;
|
AsyncWrap* GetAsyncWrap() override;
|
||||||
|
@ -90,7 +90,7 @@ void TCPWrap::Initialize(Local<Object> target,
|
|||||||
env->SetProtoMethod(t, "unref", HandleWrap::Unref);
|
env->SetProtoMethod(t, "unref", HandleWrap::Unref);
|
||||||
env->SetProtoMethod(t, "hasRef", HandleWrap::HasRef);
|
env->SetProtoMethod(t, "hasRef", HandleWrap::HasRef);
|
||||||
|
|
||||||
StreamWrap::AddMethods(env, t, StreamBase::kFlagHasWritev);
|
LibuvStreamWrap::AddMethods(env, t, StreamBase::kFlagHasWritev);
|
||||||
|
|
||||||
env->SetProtoMethod(t, "open", Open);
|
env->SetProtoMethod(t, "open", Open);
|
||||||
env->SetProtoMethod(t, "bind", Bind);
|
env->SetProtoMethod(t, "bind", Bind);
|
||||||
|
@ -183,7 +183,7 @@ void TLSWrap::Wrap(const FunctionCallbackInfo<Value>& args) {
|
|||||||
|
|
||||||
if (args.Length() < 1 || !args[0]->IsObject()) {
|
if (args.Length() < 1 || !args[0]->IsObject()) {
|
||||||
return env->ThrowTypeError(
|
return env->ThrowTypeError(
|
||||||
"First argument should be a StreamWrap instance");
|
"First argument should be a LibuvStreamWrap instance");
|
||||||
}
|
}
|
||||||
if (args.Length() < 2 || !args[1]->IsObject()) {
|
if (args.Length() < 2 || !args[1]->IsObject()) {
|
||||||
return env->ThrowTypeError(
|
return env->ThrowTypeError(
|
||||||
|
@ -63,7 +63,7 @@ void TTYWrap::Initialize(Local<Object> target,
|
|||||||
env->SetProtoMethod(t, "ref", HandleWrap::Ref);
|
env->SetProtoMethod(t, "ref", HandleWrap::Ref);
|
||||||
env->SetProtoMethod(t, "hasRef", HandleWrap::HasRef);
|
env->SetProtoMethod(t, "hasRef", HandleWrap::HasRef);
|
||||||
|
|
||||||
StreamWrap::AddMethods(env, t, StreamBase::kFlagNoShutdown);
|
LibuvStreamWrap::AddMethods(env, t, StreamBase::kFlagNoShutdown);
|
||||||
|
|
||||||
env->SetProtoMethod(t, "getWindowSize", TTYWrap::GetWindowSize);
|
env->SetProtoMethod(t, "getWindowSize", TTYWrap::GetWindowSize);
|
||||||
env->SetProtoMethod(t, "setRawMode", SetRawMode);
|
env->SetProtoMethod(t, "setRawMode", SetRawMode);
|
||||||
@ -169,10 +169,10 @@ TTYWrap::TTYWrap(Environment* env,
|
|||||||
int fd,
|
int fd,
|
||||||
bool readable,
|
bool readable,
|
||||||
int* init_err)
|
int* init_err)
|
||||||
: StreamWrap(env,
|
: LibuvStreamWrap(env,
|
||||||
object,
|
object,
|
||||||
reinterpret_cast<uv_stream_t*>(&handle_),
|
reinterpret_cast<uv_stream_t*>(&handle_),
|
||||||
AsyncWrap::PROVIDER_TTYWRAP) {
|
AsyncWrap::PROVIDER_TTYWRAP) {
|
||||||
*init_err = uv_tty_init(env->event_loop(), &handle_, fd, readable);
|
*init_err = uv_tty_init(env->event_loop(), &handle_, fd, readable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
namespace node {
|
namespace node {
|
||||||
|
|
||||||
class TTYWrap : public StreamWrap {
|
class TTYWrap : public LibuvStreamWrap {
|
||||||
public:
|
public:
|
||||||
static void Initialize(v8::Local<v8::Object> target,
|
static void Initialize(v8::Local<v8::Object> target,
|
||||||
v8::Local<v8::Value> unused,
|
v8::Local<v8::Value> unused,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user