diff options
Diffstat (limited to 'src/ClientWrite.cxx')
-rw-r--r-- | src/ClientWrite.cxx | 284 |
1 files changed, 284 insertions, 0 deletions
diff --git a/src/ClientWrite.cxx b/src/ClientWrite.cxx new file mode 100644 index 000000000..dccd256ff --- /dev/null +++ b/src/ClientWrite.cxx @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ClientInternal.hxx" + +#include <assert.h> +#include <string.h> +#include <stdio.h> + +static size_t +client_write_deferred_buffer(struct client *client, + const struct deferred_buffer *buffer) +{ + GError *error = NULL; + GIOStatus status; + gsize bytes_written; + + assert(client != NULL); + assert(client->channel != NULL); + assert(buffer != NULL); + + status = g_io_channel_write_chars + (client->channel, buffer->data, buffer->size, + &bytes_written, &error); + switch (status) { + case G_IO_STATUS_NORMAL: + return bytes_written; + + case G_IO_STATUS_AGAIN: + return 0; + + case G_IO_STATUS_EOF: + /* client has disconnected */ + + client_set_expired(client); + return 0; + + case G_IO_STATUS_ERROR: + /* I/O error */ + + client_set_expired(client); + g_warning("failed to flush buffer for %i: %s", + client->num, error->message); + g_error_free(error); + return 0; + } + + /* unreachable */ + return 0; +} + +void +client_write_deferred(struct client *client) +{ + size_t ret; + + while (!g_queue_is_empty(client->deferred_send)) { + struct deferred_buffer *buf = + (struct deferred_buffer *) + g_queue_peek_head(client->deferred_send); + + assert(buf->size > 0); + assert(buf->size <= client->deferred_bytes); + + ret = client_write_deferred_buffer(client, buf); + if (ret == 0) + break; + + if (ret < buf->size) { + assert(client->deferred_bytes >= (size_t)ret); + client->deferred_bytes -= ret; + buf->size -= ret; + memmove(buf->data, buf->data + ret, buf->size); + break; + } else { + size_t decr = sizeof(*buf) - + sizeof(buf->data) + buf->size; + + assert(client->deferred_bytes >= decr); + client->deferred_bytes -= decr; + g_free(buf); + g_queue_pop_head(client->deferred_send); + } + + g_timer_start(client->last_activity); + } + + if (g_queue_is_empty(client->deferred_send)) { + g_debug("[%u] buffer empty %lu", client->num, + (unsigned long)client->deferred_bytes); + assert(client->deferred_bytes == 0); + } +} + +static void client_defer_output(struct client *client, + const void *data, size_t length) +{ + size_t alloc; + struct deferred_buffer *buf; + + assert(length > 0); + + alloc = sizeof(*buf) - sizeof(buf->data) + length; + client->deferred_bytes += alloc; + if (client->deferred_bytes > client_max_output_buffer_size) { + g_warning("[%u] output buffer size (%lu) is " + "larger than the max (%lu)", + client->num, + (unsigned long)client->deferred_bytes, + (unsigned long)client_max_output_buffer_size); + /* cause client to close */ + client_set_expired(client); + return; + } + + buf = (struct deferred_buffer *)g_malloc(alloc); + buf->size = length; + memcpy(buf->data, data, length); + + g_queue_push_tail(client->deferred_send, buf); +} + +static void client_write_direct(struct client *client, + const char *data, size_t length) +{ + GError *error = NULL; + GIOStatus status; + gsize bytes_written; + + assert(client != NULL); + assert(client->channel != NULL); + assert(data != NULL); + assert(length > 0); + assert(g_queue_is_empty(client->deferred_send)); + + status = g_io_channel_write_chars(client->channel, data, length, + &bytes_written, &error); + switch (status) { + case G_IO_STATUS_NORMAL: + case G_IO_STATUS_AGAIN: + break; + + case G_IO_STATUS_EOF: + /* client has disconnected */ + + client_set_expired(client); + return; + + case G_IO_STATUS_ERROR: + /* I/O error */ + + client_set_expired(client); + g_warning("failed to write to %i: %s", + client->num, error->message); + g_error_free(error); + return; + } + + if (bytes_written < length) + client_defer_output(client, data + bytes_written, + length - bytes_written); + + if (!g_queue_is_empty(client->deferred_send)) + g_debug("[%u] buffer created", client->num); +} + +void +client_write_output(struct client *client) +{ + if (client_is_expired(client) || !client->send_buf_used) + return; + + if (!g_queue_is_empty(client->deferred_send)) { + client_defer_output(client, client->send_buf, + client->send_buf_used); + + if (client_is_expired(client)) + return; + + /* try to flush the deferred buffers now; the current + server command may take too long to finish, and + meanwhile try to feed output to the client, + otherwise it will time out. One reason why + deferring is slow might be that currently each + client_write() allocates a new deferred buffer. + This should be optimized after MPD 0.14. */ + client_write_deferred(client); + } else + client_write_direct(client, client->send_buf, + client->send_buf_used); + + client->send_buf_used = 0; +} + +/** + * Write a block of data to the client. + */ +static void client_write(struct client *client, const char *buffer, size_t buflen) +{ + /* if the client is going to be closed, do nothing */ + if (client_is_expired(client)) + return; + + while (buflen > 0 && !client_is_expired(client)) { + size_t copylen; + + assert(client->send_buf_used < sizeof(client->send_buf)); + + copylen = sizeof(client->send_buf) - client->send_buf_used; + if (copylen > buflen) + copylen = buflen; + + memcpy(client->send_buf + client->send_buf_used, buffer, + copylen); + buflen -= copylen; + client->send_buf_used += copylen; + buffer += copylen; + if (client->send_buf_used >= sizeof(client->send_buf)) + client_write_output(client); + } +} + +void client_puts(struct client *client, const char *s) +{ + client_write(client, s, strlen(s)); +} + +void client_vprintf(struct client *client, const char *fmt, va_list args) +{ +#ifndef G_OS_WIN32 + va_list tmp; + int length; + + va_copy(tmp, args); + length = vsnprintf(NULL, 0, fmt, tmp); + va_end(tmp); + + if (length <= 0) + /* wtf.. */ + return; + + char *buffer = (char *)g_malloc(length + 1); + vsnprintf(buffer, length + 1, fmt, args); + client_write(client, buffer, length); + g_free(buffer); +#else + /* On mingw32, snprintf() expects a 64 bit integer instead of + a "long int" for "%li". This is not consistent with our + expectation, so we're using plain sprintf() here, hoping + the static buffer is large enough. Sorry for this hack, + but WIN32 development is so painful, I'm not in the mood to + do it properly now. */ + + static char buffer[4096]; + vsprintf(buffer, fmt, args); + client_write(client, buffer, strlen(buffer)); +#endif +} + +G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + client_vprintf(client, fmt, args); + va_end(args); +} |