diff options
author | Max Kellermann <max@duempel.org> | 2011-01-29 09:26:22 +0100 |
---|---|---|
committer | Max Kellermann <max@duempel.org> | 2011-01-29 10:43:54 +0100 |
commit | f8b09c194fe20192c4ac45697e9d0f00e8a96c2c (patch) | |
tree | d45c30ce117a7e01431078305ffe2e96d016dc5c /src | |
parent | 0e69ad32c16eb6449a8952f894c6f239f2e2c52f (diff) | |
download | mpd-f8b09c194fe20192c4ac45697e9d0f00e8a96c2c.tar.gz mpd-f8b09c194fe20192c4ac45697e9d0f00e8a96c2c.tar.xz mpd-f8b09c194fe20192c4ac45697e9d0f00e8a96c2c.zip |
protocol: support client-to-client communication
Diffstat (limited to 'src')
-rw-r--r-- | src/client_internal.h | 28 | ||||
-rw-r--r-- | src/client_message.c | 96 | ||||
-rw-r--r-- | src/client_message.h | 72 | ||||
-rw-r--r-- | src/client_new.c | 4 | ||||
-rw-r--r-- | src/client_subscribe.c | 123 | ||||
-rw-r--r-- | src/client_subscribe.h | 59 | ||||
-rw-r--r-- | src/command.c | 172 | ||||
-rw-r--r-- | src/idle.c | 2 | ||||
-rw-r--r-- | src/idle.h | 6 |
9 files changed, 562 insertions, 0 deletions
diff --git a/src/client_internal.h b/src/client_internal.h index d675ed7c6..ba97e4b8f 100644 --- a/src/client_internal.h +++ b/src/client_internal.h @@ -21,11 +21,17 @@ #define MPD_CLIENT_INTERNAL_H #include "client.h" +#include "client_message.h" #include "command.h" #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "client" +enum { + CLIENT_MAX_SUBSCRIPTIONS = 16, + CLIENT_MAX_MESSAGES = 64, +}; + struct deferred_buffer { size_t size; char data[sizeof(long)]; @@ -69,6 +75,28 @@ struct client { /** idle flags that the client wants to receive */ unsigned idle_subscriptions; + + /** + * A list of channel names this client is subscribed to. + */ + GSList *subscriptions; + + /** + * The number of subscriptions in #subscriptions. Used to + * limit the number of subscriptions. + */ + unsigned num_subscriptions; + + /** + * A list of messages this client has received in reverse + * order (latest first). + */ + GSList *messages; + + /** + * The number of messages in #messages. + */ + unsigned num_messages; }; extern unsigned int client_max_connections; diff --git a/src/client_message.c b/src/client_message.c new file mode 100644 index 000000000..b681b4e7f --- /dev/null +++ b/src/client_message.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2003-2011 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 "client_message.h" + +#include <assert.h> +#include <glib.h> + +G_GNUC_PURE +static bool +valid_channel_char(const char ch) +{ + return g_ascii_isalnum(ch) || + ch == '_' || ch == '-' || ch == '.' || ch == ':'; +} + +bool +client_message_valid_channel_name(const char *name) +{ + do { + if (!valid_channel_char(*name)) + return false; + } while (*++name != 0); + + return true; +} + +void +client_message_init_null(struct client_message *msg) +{ + assert(msg != NULL); + + msg->channel = NULL; + msg->message = NULL; +} + +void +client_message_init(struct client_message *msg, + const char *channel, const char *message) +{ + assert(msg != NULL); + + msg->channel = g_strdup(channel); + msg->message = g_strdup(message); +} + +void +client_message_copy(struct client_message *dest, + const struct client_message *src) +{ + assert(dest != NULL); + assert(src != NULL); + assert(client_message_defined(src)); + + client_message_init(dest, src->channel, src->message); +} + +struct client_message * +client_message_dup(const struct client_message *src) +{ + struct client_message *dest = g_slice_new(struct client_message); + client_message_copy(dest, src); + return dest; +} + +void +client_message_deinit(struct client_message *msg) +{ + assert(msg != NULL); + + g_free(msg->channel); + g_free(msg->message); +} + +void +client_message_free(struct client_message *msg) +{ + client_message_deinit(msg); + g_slice_free(struct client_message, msg); +} diff --git a/src/client_message.h b/src/client_message.h new file mode 100644 index 000000000..5c7e86c15 --- /dev/null +++ b/src/client_message.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2003-2011 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. + */ + +#ifndef MPD_CLIENT_MESSAGE_H +#define MPD_CLIENT_MESSAGE_H + +#include <assert.h> +#include <stdbool.h> +#include <stddef.h> +#include <glib.h> + +/** + * A client-to-client message. + */ +struct client_message { + char *channel; + + char *message; +}; + +G_GNUC_PURE +bool +client_message_valid_channel_name(const char *name); + +G_GNUC_PURE +static inline bool +client_message_defined(const struct client_message *msg) +{ + assert(msg != NULL); + assert((msg->channel == NULL) == (msg->message == NULL)); + + return msg->channel != NULL; +} + +void +client_message_init_null(struct client_message *msg); + +void +client_message_init(struct client_message *msg, + const char *channel, const char *message); + +void +client_message_copy(struct client_message *dest, + const struct client_message *src); + +G_GNUC_MALLOC G_GNUC_PURE +struct client_message * +client_message_dup(const struct client_message *src); + +void +client_message_deinit(struct client_message *msg); + +void +client_message_free(struct client_message *msg); + +#endif diff --git a/src/client_new.c b/src/client_new.c index ffe7c7ce6..5b2dfde65 100644 --- a/src/client_new.c +++ b/src/client_new.c @@ -121,6 +121,10 @@ client_new(struct player_control *player_control, client->send_buf_used = 0; + client->subscriptions = NULL; + client->messages = NULL; + client->num_messages = 0; + (void)send(fd, GREETING, sizeof(GREETING) - 1, 0); client_list_add(client); diff --git a/src/client_subscribe.c b/src/client_subscribe.c new file mode 100644 index 000000000..c65a7ed31 --- /dev/null +++ b/src/client_subscribe.c @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2003-2011 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 "client_subscribe.h" +#include "client_internal.h" +#include "client_idle.h" +#include "idle.h" + +#include <string.h> + +G_GNUC_PURE +static GSList * +client_find_subscription(const struct client *client, const char *channel) +{ + for (GSList *i = client->subscriptions; i != NULL; i = g_slist_next(i)) + if (strcmp((const char *)i->data, channel) == 0) + return i; + + return NULL; +} + +enum client_subscribe_result +client_subscribe(struct client *client, const char *channel) +{ + assert(client != NULL); + assert(channel != NULL); + + if (!client_message_valid_channel_name(channel)) + return CLIENT_SUBSCRIBE_INVALID; + + if (client_find_subscription(client, channel) != NULL) + return CLIENT_SUBSCRIBE_ALREADY; + + if (client->num_subscriptions >= CLIENT_MAX_SUBSCRIPTIONS) + return CLIENT_SUBSCRIBE_FULL; + + client->subscriptions = g_slist_prepend(client->subscriptions, + g_strdup(channel)); + ++client->num_subscriptions; + + idle_add(IDLE_SUBSCRIPTION); + + return CLIENT_SUBSCRIBE_OK; +} + +bool +client_unsubscribe(struct client *client, const char *channel) +{ + GSList *i = client_find_subscription(client, channel); + if (i == NULL) + return false; + + assert(client->num_subscriptions > 0); + + client->subscriptions = g_slist_remove(client->subscriptions, i->data); + --client->num_subscriptions; + + idle_add(IDLE_SUBSCRIPTION); + + assert((client->num_subscriptions == 0) == + (client->subscriptions == NULL)); + + return true; +} + +void +client_unsubscribe_all(struct client *client) +{ + for (GSList *i = client->subscriptions; i != NULL; i = g_slist_next(i)) + g_free(i->data); + + g_slist_free(client->subscriptions); + client->subscriptions = NULL; + client->num_subscriptions = 0; +} + +bool +client_push_message(struct client *client, const struct client_message *msg) +{ + assert(client != NULL); + assert(msg != NULL); + assert(client_message_defined(msg)); + + if (client->num_messages >= CLIENT_MAX_MESSAGES || + client_find_subscription(client, msg->channel) == NULL) + return false; + + if (client->messages == NULL) + client_idle_add(client, IDLE_MESSAGE); + + client->messages = g_slist_prepend(client->messages, + client_message_dup(msg)); + ++client->num_messages; + + return true; +} + +GSList * +client_read_messages(struct client *client) +{ + GSList *messages = g_slist_reverse(client->messages); + + client->messages = NULL; + client->num_messages = 0; + + return messages; +} diff --git a/src/client_subscribe.h b/src/client_subscribe.h new file mode 100644 index 000000000..09f864417 --- /dev/null +++ b/src/client_subscribe.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2011 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. + */ + +#ifndef MPD_CLIENT_SUBSCRIBE_H +#define MPD_CLIENT_SUBSCRIBE_H + +#include <stdbool.h> +#include <glib.h> + +struct client; +struct client_message; + +enum client_subscribe_result { + /** success */ + CLIENT_SUBSCRIBE_OK, + + /** invalid channel name */ + CLIENT_SUBSCRIBE_INVALID, + + /** already subscribed to this channel */ + CLIENT_SUBSCRIBE_ALREADY, + + /** too many subscriptions */ + CLIENT_SUBSCRIBE_FULL, +}; + +enum client_subscribe_result +client_subscribe(struct client *client, const char *channel); + +bool +client_unsubscribe(struct client *client, const char *channel); + +void +client_unsubscribe_all(struct client *client); + +bool +client_push_message(struct client *client, const struct client_message *msg); + +G_GNUC_MALLOC +GSList * +client_read_messages(struct client *client); + +#endif diff --git a/src/command.c b/src/command.c index 354bf85f5..aeed55a1b 100644 --- a/src/command.c +++ b/src/command.c @@ -46,6 +46,7 @@ #include "client.h" #include "client_idle.h" #include "client_internal.h" +#include "client_subscribe.h" #include "tag_print.h" #include "path.h" #include "replay_gain_config.h" @@ -1837,6 +1838,172 @@ handle_sticker(struct client *client, int argc, char *argv[]) } #endif +static enum command_return +handle_subscribe(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + assert(argc == 2); + + switch (client_subscribe(client, argv[1])) { + case CLIENT_SUBSCRIBE_OK: + return COMMAND_RETURN_OK; + + case CLIENT_SUBSCRIBE_INVALID: + command_error(client, ACK_ERROR_ARG, + "invalid channel name"); + return COMMAND_RETURN_ERROR; + + case CLIENT_SUBSCRIBE_ALREADY: + command_error(client, ACK_ERROR_EXIST, + "already subscribed to this channel"); + return COMMAND_RETURN_ERROR; + + case CLIENT_SUBSCRIBE_FULL: + command_error(client, ACK_ERROR_EXIST, + "subscription list is full"); + return COMMAND_RETURN_ERROR; + } + + /* unreachable */ + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_unsubscribe(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + assert(argc == 2); + + if (client_unsubscribe(client, argv[1])) + return COMMAND_RETURN_OK; + else { + command_error(client, ACK_ERROR_NO_EXIST, + "not subscribed to this channel"); + return COMMAND_RETURN_ERROR; + } +} + +struct channels_context { + GStringChunk *chunk; + + GHashTable *channels; +}; + +static void +collect_channels(gpointer data, gpointer user_data) +{ + struct channels_context *context = user_data; + const struct client *client = data; + + for (GSList *i = client->subscriptions; i != NULL; + i = g_slist_next(i)) { + const char *channel = i->data; + + if (g_hash_table_lookup(context->channels, channel) == NULL) { + char *channel2 = g_string_chunk_insert(context->chunk, + channel); + g_hash_table_insert(context->channels, channel2, + context); + } + } +} + +static void +print_channel(gpointer key, G_GNUC_UNUSED gpointer value, gpointer user_data) +{ + struct client *client = user_data; + const char *channel = key; + + client_printf(client, "channel: %s\n", channel); +} + +static enum command_return +handle_channels(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + assert(argc == 1); + + struct channels_context context = { + .chunk = g_string_chunk_new(1024), + .channels = g_hash_table_new(g_str_hash, g_str_equal), + }; + + client_list_foreach(collect_channels, &context); + + g_hash_table_foreach(context.channels, print_channel, client); + + g_hash_table_destroy(context.channels); + g_string_chunk_free(context.chunk); + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_read_messages(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + assert(argc == 1); + + GSList *messages = client_read_messages(client); + + for (GSList *i = messages; i != NULL; i = g_slist_next(i)) { + struct client_message *msg = i->data; + + client_printf(client, "channel: %s\nmessage: %s\n", + msg->channel, msg->message); + client_message_free(msg); + } + + g_slist_free(messages); + + return COMMAND_RETURN_OK; +} + +struct send_message_context { + struct client_message msg; + + bool sent; +}; + +static void +send_message(gpointer data, gpointer user_data) +{ + struct send_message_context *context = user_data; + struct client *client = data; + + if (client_push_message(client, &context->msg)) + context->sent = true; +} + +static enum command_return +handle_send_message(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + assert(argc == 3); + + if (!client_message_valid_channel_name(argv[1])) { + command_error(client, ACK_ERROR_ARG, + "invalid channel name"); + return COMMAND_RETURN_ERROR; + } + + struct send_message_context context = { + .sent = false, + }; + + client_message_init(&context.msg, argv[1], argv[2]); + + client_list_foreach(send_message, &context); + + client_message_deinit(&context.msg); + + if (context.sent) + return COMMAND_RETURN_OK; + else { + command_error(client, ACK_ERROR_NO_EXIST, + "nobody is subscribed to this channel"); + return COMMAND_RETURN_ERROR; + } +} + /** * The command registry. * @@ -1845,6 +2012,7 @@ handle_sticker(struct client *client, int argc, char *argv[]) static const struct command commands[] = { { "add", PERMISSION_ADD, 1, 1, handle_add }, { "addid", PERMISSION_ADD, 1, 2, handle_addid }, + { "channels", PERMISSION_READ, 0, 0, handle_channels }, { "clear", PERMISSION_CONTROL, 0, 0, handle_clear }, { "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror }, { "close", PERMISSION_NONE, -1, -1, handle_close }, @@ -1895,6 +2063,7 @@ static const struct command commands[] = { { "plchangesposid", PERMISSION_READ, 1, 1, handle_plchangesposid }, { "previous", PERMISSION_CONTROL, 0, 0, handle_previous }, { "random", PERMISSION_CONTROL, 1, 1, handle_random }, + { "readmessages", PERMISSION_READ, 0, 0, handle_read_messages }, { "rename", PERMISSION_CONTROL, 2, 2, handle_rename }, { "repeat", PERMISSION_CONTROL, 1, 1, handle_repeat }, { "replay_gain_mode", PERMISSION_CONTROL, 1, 1, @@ -1907,6 +2076,7 @@ static const struct command commands[] = { { "search", PERMISSION_READ, 2, -1, handle_search }, { "seek", PERMISSION_CONTROL, 2, 2, handle_seek }, { "seekid", PERMISSION_CONTROL, 2, 2, handle_seekid }, + { "sendmessage", PERMISSION_CONTROL, 2, 2, handle_send_message }, { "setvol", PERMISSION_CONTROL, 1, 1, handle_setvol }, { "shuffle", PERMISSION_CONTROL, 0, 1, handle_shuffle }, { "single", PERMISSION_CONTROL, 1, 1, handle_single }, @@ -1916,9 +2086,11 @@ static const struct command commands[] = { { "sticker", PERMISSION_ADMIN, 3, -1, handle_sticker }, #endif { "stop", PERMISSION_CONTROL, 0, 0, handle_stop }, + { "subscribe", PERMISSION_READ, 1, 1, handle_subscribe }, { "swap", PERMISSION_CONTROL, 2, 2, handle_swap }, { "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid }, { "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes }, + { "unsubscribe", PERMISSION_READ, 1, 1, handle_unsubscribe }, { "update", PERMISSION_ADMIN, 0, 1, handle_update }, { "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers }, }; diff --git a/src/idle.c b/src/idle.c index 7b9f658c6..2d174d78a 100644 --- a/src/idle.c +++ b/src/idle.c @@ -42,6 +42,8 @@ static const char *const idle_names[] = { "options", "sticker", "update", + "subscription", + "message", NULL }; diff --git a/src/idle.h b/src/idle.h index 52adc4d6e..0156933c0 100644 --- a/src/idle.h +++ b/src/idle.h @@ -53,6 +53,12 @@ enum { /** a database update has started or finished. */ IDLE_UPDATE = 0x100, + + /** a client has subscribed or unsubscribed to/from a channel */ + IDLE_SUBSCRIPTION = 0x200, + + /** a message on the subscribed channel was receivedd */ + IDLE_MESSAGE = 0x400, }; /** |