diff options
| author | user <user@mail.com> | 2025-07-09 18:43:35 -0400 |
|---|---|---|
| committer | user <user@mail.com> | 2025-07-09 18:44:18 -0400 |
| commit | 1e8a4b50adeb773e946bc4101d4fb6e983f6c160 (patch) | |
| tree | 1d66cfbbaf6084006c0be13079bca6754ea271c5 | |
| parent | 54b2e7496154d6e0f5926a2bb4c87c41a0475d50 (diff) | |
refactor
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | main.c | 2 | ||||
| -rw-r--r-- | mpd_ws.c | 351 | ||||
| -rw-r--r-- | mpdws.c | 244 | ||||
| -rw-r--r-- | mpdws.h (renamed from mpd_ws.h) | 0 |
5 files changed, 246 insertions, 353 deletions
@@ -1,7 +1,7 @@ .PHONY: all clean install uninstall PROG= mpdws -SRCS= mpd_ws.h mpd_ws.c main.c +SRCS= mpdws.h mpdws.c main.c NOMAN= CPPFLAGS+= -I/usr/local/include @@ -1,4 +1,4 @@ -#include "mpd_ws.h" +#include "mpdws.h" #include <stdio.h> int main() { diff --git a/mpd_ws.c b/mpd_ws.c deleted file mode 100644 index c24cb6b..0000000 --- a/mpd_ws.c +++ /dev/null @@ -1,351 +0,0 @@ -#include "mpd_ws.h" -#include <signal.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/select.h> -#include <syslog.h> -#include <time.h> -#include <unistd.h> - -/* Global server instance for signal handling */ -static struct mpd_ws_server *g_server = NULL; - -/* Signal handler */ -static void signal_handler(int sig) { - (void)sig; /* Suppress unused param warning */ - if (g_server) { - g_server->running = 0; - } -} - -/* WebSocket callback */ -static int ws_callback(struct lws *wsi, enum lws_callback_reasons reason, - void *user, void *in, size_t len) { - (void)user; /* Suppress unused param warnings */ - (void)in; - (void)len; - - struct mpd_ws_server *server = - (struct mpd_ws_server *)lws_context_user(lws_get_context(wsi)); - - switch (reason) { - case LWS_CALLBACK_ESTABLISHED: - client_add(server, wsi); - /* Send current song to new client */ - if (strlen(server->current_song) > 0) { - lws_callback_on_writable(wsi); - } - break; - - case LWS_CALLBACK_CLOSED: - client_remove(server, wsi); - break; - - case LWS_CALLBACK_SERVER_WRITEABLE: - if (strlen(server->current_song) > 0) { - unsigned char buf[LWS_PRE + MAX_MESSAGE_SIZE]; - size_t msg_len = strlen(server->current_song); - memcpy(&buf[LWS_PRE], server->current_song, msg_len); - lws_write(wsi, &buf[LWS_PRE], msg_len, LWS_WRITE_TEXT); - } - break; - - case LWS_CALLBACK_RECEIVE: - /* Ignore client input */ - break; - - default: - break; - } - - return 0; -} - -/* WebSocket protocols */ -static struct lws_protocols protocols[] = { - {"mpd-protocol", ws_callback, 0, MAX_MESSAGE_SIZE, 0, NULL, 0}, - {NULL, NULL, 0, 0, 0, NULL, 0}}; - -/* Initialize the server */ -int mpd_ws_init(struct mpd_ws_server *server) { - memset(server, 0, sizeof(*server)); - - /* Setup signal handlers */ - g_server = server; - signal(SIGINT, signal_handler); - signal(SIGTERM, signal_handler); - - /* Initialize syslog */ - openlog("mpd_ws", LOG_PID | LOG_NDELAY, LOG_DAEMON); - - /* Create WebSocket context */ - struct lws_context_creation_info info = {0}; - info.port = WEBSOCKET_PORT; - info.protocols = protocols; - info.gid = -1; - info.uid = -1; - info.user = server; - - server->ws_context = lws_create_context(&info); - if (!server->ws_context) { - syslog(LOG_ERR, "Failed to create WebSocket context"); - return -1; - } - - syslog(LOG_INFO, "WebSocket server listening on port %d", WEBSOCKET_PORT); - - /* Connect to MPD */ - mpd_ws_connect(server); - - server->running = 1; - return 0; -} - -/* Main server loop */ -void mpd_ws_run(struct mpd_ws_server *server) { - time_t last_reconnect = 0; - - while (server->running) { - /* Service WebSocket events */ - lws_service(server->ws_context, 10); - - /* Handle MPD reconnection */ - if (!mpd_ws_is_connected(server)) { - time_t now = time(NULL); - if (now - last_reconnect >= RECONNECT_INTERVAL_SEC) { - if (mpd_ws_connect(server) == 0) { - mpd_ws_update_song(server); - mpd_ws_start_idle(server); - } - last_reconnect = now; - } - sleep(SELECT_TIMEOUT_SEC); - continue; - } - - /* Start idle mode if not active */ - if (!server->mpd_idle_active) { - if (mpd_ws_start_idle(server) < 0) { - mpd_ws_disconnect(server); - continue; - } - } - - /* Check for MPD events */ - fd_set readfds; - int mpd_fd = mpd_connection_get_fd(server->mpd_conn); - struct timeval timeout = {0, SELECT_TIMEOUT_SEC}; - - FD_ZERO(&readfds); - FD_SET(mpd_fd, &readfds); - - int result = select(mpd_fd + 1, &readfds, NULL, NULL, &timeout); - if (result > 0 && FD_ISSET(mpd_fd, &readfds)) { - mpd_ws_process_idle(server); - if (!mpd_ws_is_connected(server)) { - mpd_ws_disconnect(server); - } - } - } -} - -/* Cleanup and shutdown */ -void mpd_ws_cleanup(struct mpd_ws_server *server) { - syslog(LOG_INFO, "Shutting down"); - - mpd_ws_disconnect(server); - - if (server->ws_context) { - lws_context_destroy(server->ws_context); - } - - /* Free client list */ - while (server->clients) { - struct client_session *next = server->clients->next; - free(server->clients); - server->clients = next; - } - - closelog(); -} - -/* Stop the server */ -void mpd_ws_stop(struct mpd_ws_server *server) { server->running = 0; } - -/* Connect to MPD */ -int mpd_ws_connect(struct mpd_ws_server *server) { - if (server->mpd_conn) { - mpd_connection_free(server->mpd_conn); - } - - server->mpd_conn = mpd_connection_new(MPD_HOST, MPD_PORT, 0); - if (mpd_connection_get_error(server->mpd_conn) != MPD_ERROR_SUCCESS) { - syslog(LOG_ERR, "Failed to connect to MPD: %s", - mpd_connection_get_error_message(server->mpd_conn)); - mpd_connection_free(server->mpd_conn); - server->mpd_conn = NULL; - return -1; - } - - server->mpd_idle_active = 0; - syslog(LOG_INFO, "Connected to MPD at %s:%d", MPD_HOST, MPD_PORT); - return 0; -} - -/* Disconnect from MPD */ -void mpd_ws_disconnect(struct mpd_ws_server *server) { - if (server->mpd_conn) { - if (server->mpd_idle_active) { - mpd_send_noidle(server->mpd_conn); - mpd_response_finish(server->mpd_conn); - } - mpd_connection_free(server->mpd_conn); - server->mpd_conn = NULL; - } - server->mpd_idle_active = 0; -} - -/* Check if MPD is connected */ -int mpd_ws_is_connected(struct mpd_ws_server *server) { - return server->mpd_conn && - mpd_connection_get_error(server->mpd_conn) == MPD_ERROR_SUCCESS; -} - -/* Update current song and broadcast */ -void mpd_ws_update_song(struct mpd_ws_server *server) { - if (!mpd_ws_is_connected(server)) { - return; - } - - mpd_command_list_begin(server->mpd_conn, false); - mpd_send_current_song(server->mpd_conn); - mpd_command_list_end(server->mpd_conn); - - struct mpd_song *song = mpd_recv_song(server->mpd_conn); - if (mpd_connection_get_error(server->mpd_conn) != MPD_ERROR_SUCCESS) { - syslog(LOG_ERR, "Failed to get current song: %s", - mpd_connection_get_error_message(server->mpd_conn)); - return; - } - - /* Create song message */ - if (song == NULL) { - snprintf(server->current_song, MAX_MESSAGE_SIZE, "Now Playing: No song"); - } else { - const char *artist = mpd_song_get_tag(song, MPD_TAG_ARTIST, 0); - const char *title = mpd_song_get_tag(song, MPD_TAG_TITLE, 0); - - if (artist && title) { - snprintf(server->current_song, MAX_MESSAGE_SIZE, "Now Playing: %s - %s", - artist, title); - } else if (title) { - snprintf(server->current_song, MAX_MESSAGE_SIZE, "Now Playing: %s", - title); - } else { - const char *uri = mpd_song_get_uri(song); - snprintf(server->current_song, MAX_MESSAGE_SIZE, "Now Playing: %s", - uri ? uri : "Unknown"); - } - } - - syslog(LOG_DEBUG, "Broadcasting: %s", server->current_song); - - /* Only broadcast if song actually changed */ - if (strcmp(server->current_song, server->previous_song) != 0) { - client_broadcast(server, server->current_song); - strlcpy(server->previous_song, server->current_song, - sizeof(server->previous_song)); - } - - if (song) { - mpd_song_free(song); - } - mpd_response_finish(server->mpd_conn); -} - -/* Start MPD idle mode */ -int mpd_ws_start_idle(struct mpd_ws_server *server) { - if (!mpd_ws_is_connected(server)) { - return -1; - } - - if (!mpd_send_idle_mask(server->mpd_conn, MPD_IDLE_PLAYER)) { - syslog(LOG_ERR, "Failed to send idle command: %s", - mpd_connection_get_error_message(server->mpd_conn)); - return -1; - } - - server->mpd_idle_active = 1; - return 0; -} - -/* Process MPD idle response */ -void mpd_ws_process_idle(struct mpd_ws_server *server) { - if (!mpd_ws_is_connected(server) || !server->mpd_idle_active) { - return; - } - - enum mpd_idle events = mpd_recv_idle(server->mpd_conn, false); - server->mpd_idle_active = 0; - - if (mpd_connection_get_error(server->mpd_conn) != MPD_ERROR_SUCCESS) { - syslog(LOG_WARNING, "MPD idle error: %s", - mpd_connection_get_error_message(server->mpd_conn)); - return; - } - - if (events & MPD_IDLE_PLAYER) { - mpd_ws_update_song(server); - } - - /* Restart idle mode */ - mpd_ws_start_idle(server); -} - -/* Add client */ -void client_add(struct mpd_ws_server *server, struct lws *wsi) { - struct client_session *client = malloc(sizeof(struct client_session)); - if (!client) { - syslog(LOG_ERR, "Failed to allocate client session"); - return; - } - - client->wsi = wsi; - client->next = server->clients; - server->clients = client; - - syslog(LOG_INFO, "Client connected"); -} - -/* Remove client */ -void client_remove(struct mpd_ws_server *server, struct lws *wsi) { - struct client_session **current = &server->clients; - - while (*current) { - if ((*current)->wsi == wsi) { - struct client_session *to_remove = *current; - *current = (*current)->next; - free(to_remove); - syslog(LOG_INFO, "Client disconnected"); - return; - } - current = &(*current)->next; - } -} - -/* Broadcast message to all clients */ -void client_broadcast(struct mpd_ws_server *server, const char *message) { - struct client_session *client = server->clients; - size_t len = strlen(message); - - while (client) { - unsigned char buf[LWS_PRE + MAX_MESSAGE_SIZE]; - memcpy(&buf[LWS_PRE], message, len); - - if (lws_write(client->wsi, &buf[LWS_PRE], len, LWS_WRITE_TEXT) < 0) { - syslog(LOG_WARNING, "Failed to write to websocket"); - } - client = client->next; - } -} @@ -0,0 +1,244 @@ +#include "mpdws.h" +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/select.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> + +static struct mpd_ws_server *g_server = NULL; + +static void signal_handler(int sig) { + (void)sig; + if (g_server) + g_server->running = 0; +} + +static void broadcast_current_song(struct mpd_ws_server *server) { + struct client_session *client = server->clients; + size_t len = strlen(server->current_song); + + while (client) { + unsigned char buf[LWS_PRE + MAX_MESSAGE_SIZE]; + memcpy(&buf[LWS_PRE], server->current_song, len); + lws_write(client->wsi, &buf[LWS_PRE], len, LWS_WRITE_TEXT); + client = client->next; + } +} + +static int ws_callback(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) { + (void)user; + (void)in; + (void)len; + + struct mpd_ws_server *server = + (struct mpd_ws_server *)lws_context_user(lws_get_context(wsi)); + + switch (reason) { + case LWS_CALLBACK_ESTABLISHED: { + struct client_session *client = malloc(sizeof(*client)); + if (client) { + client->wsi = wsi; + client->next = server->clients; + server->clients = client; + syslog(LOG_INFO, "Client connected"); + if (strlen(server->current_song) > 0) { + lws_callback_on_writable(wsi); + } + } + break; + } + case LWS_CALLBACK_CLOSED: { + struct client_session **current = &server->clients; + while (*current) { + if ((*current)->wsi == wsi) { + struct client_session *to_remove = *current; + *current = (*current)->next; + free(to_remove); + syslog(LOG_INFO, "Client disconnected"); + break; + } + current = &(*current)->next; + } + break; + } + case LWS_CALLBACK_SERVER_WRITEABLE: + if (strlen(server->current_song) > 0) { + unsigned char buf[LWS_PRE + MAX_MESSAGE_SIZE]; + size_t msg_len = strlen(server->current_song); + memcpy(&buf[LWS_PRE], server->current_song, msg_len); + lws_write(wsi, &buf[LWS_PRE], msg_len, LWS_WRITE_TEXT); + } + break; + } + return 0; +} + +static struct lws_protocols protocols[] = { + {"mpd-protocol", ws_callback, 0, MAX_MESSAGE_SIZE, 0, NULL, 0}, + {NULL, NULL, 0, 0, 0, NULL, 0}}; + +static int connect_mpd(struct mpd_ws_server *server) { + if (server->mpd_conn) { + if (server->mpd_idle_active) { + mpd_send_noidle(server->mpd_conn); + mpd_response_finish(server->mpd_conn); + } + mpd_connection_free(server->mpd_conn); + } + + server->mpd_conn = mpd_connection_new(MPD_HOST, MPD_PORT, 0); + server->mpd_idle_active = 0; + + if (mpd_connection_get_error(server->mpd_conn) != MPD_ERROR_SUCCESS) { + syslog(LOG_ERR, "MPD connection failed: %s", + mpd_connection_get_error_message(server->mpd_conn)); + mpd_connection_free(server->mpd_conn); + server->mpd_conn = NULL; + return -1; + } + + syslog(LOG_INFO, "Connected to MPD at %s:%d", MPD_HOST, MPD_PORT); + return 0; +} + +static void update_current_song(struct mpd_ws_server *server) { + if (!server->mpd_conn || + mpd_connection_get_error(server->mpd_conn) != MPD_ERROR_SUCCESS) { + return; + } + + mpd_send_current_song(server->mpd_conn); + struct mpd_song *song = mpd_recv_song(server->mpd_conn); + + if (mpd_connection_get_error(server->mpd_conn) != MPD_ERROR_SUCCESS) { + mpd_response_finish(server->mpd_conn); + return; + } + + char new_song[MAX_MESSAGE_SIZE]; + if (!song) { + snprintf(new_song, sizeof(new_song), "Now Playing: No song"); + } else { + const char *artist = mpd_song_get_tag(song, MPD_TAG_ARTIST, 0); + const char *title = mpd_song_get_tag(song, MPD_TAG_TITLE, 0); + + snprintf(new_song, sizeof(new_song), "Now Playing: %s - %s", artist, title); + mpd_song_free(song); + } + + // Only send song if it's changed + if (strcmp(new_song, server->current_song) != 0) { + strlcpy(server->current_song, new_song, sizeof(server->current_song)); + broadcast_current_song(server); + syslog(LOG_DEBUG, "Broadcasting: %s", server->current_song); + } + + mpd_response_finish(server->mpd_conn); +} + +int mpd_ws_init(struct mpd_ws_server *server) { + memset(server, 0, sizeof(*server)); + + g_server = server; + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + openlog("mpd_ws", LOG_PID | LOG_NDELAY, LOG_DAEMON); + + struct lws_context_creation_info info = {0}; + info.port = WEBSOCKET_PORT; + info.protocols = protocols; + info.gid = info.uid = -1; + info.user = server; + + server->ws_context = lws_create_context(&info); + if (!server->ws_context) { + syslog(LOG_ERR, "Failed to create WebSocket context"); + return -1; + } + + syslog(LOG_INFO, "WebSocket server listening on port %d", WEBSOCKET_PORT); + connect_mpd(server); + server->running = 1; + return 0; +} + +void mpd_ws_run(struct mpd_ws_server *server) { + time_t last_reconnect = 0; + + while (server->running) { + lws_service(server->ws_context, 10); + + // Handle MPD connection + if (!server->mpd_conn || + mpd_connection_get_error(server->mpd_conn) != MPD_ERROR_SUCCESS) { + time_t now = time(NULL); + if (now - last_reconnect >= RECONNECT_INTERVAL_SEC) { + if (connect_mpd(server) == 0) { + update_current_song(server); + } + last_reconnect = now; + } + sleep(SELECT_TIMEOUT_SEC); + continue; + } + + // Start idle if needed + if (!server->mpd_idle_active) { + if (mpd_send_idle_mask(server->mpd_conn, MPD_IDLE_PLAYER)) { + server->mpd_idle_active = 1; + } else { + connect_mpd(server); + continue; + } + } + + // Check for MPD events + fd_set readfds; + int mpd_fd = mpd_connection_get_fd(server->mpd_conn); + struct timeval timeout = {0, SELECT_TIMEOUT_SEC}; + + FD_ZERO(&readfds); + FD_SET(mpd_fd, &readfds); + + if (select(mpd_fd + 1, &readfds, NULL, NULL, &timeout) > 0 && + FD_ISSET(mpd_fd, &readfds)) { + + enum mpd_idle events = mpd_recv_idle(server->mpd_conn, false); + server->mpd_idle_active = 0; + + if (mpd_connection_get_error(server->mpd_conn) == MPD_ERROR_SUCCESS) { + if (events & MPD_IDLE_PLAYER) { + update_current_song(server); + } + } + } + } +} + +void mpd_ws_cleanup(struct mpd_ws_server *server) { + syslog(LOG_INFO, "Shutting down"); + + if (server->mpd_conn) { + if (server->mpd_idle_active) { + mpd_send_noidle(server->mpd_conn); + mpd_response_finish(server->mpd_conn); + } + mpd_connection_free(server->mpd_conn); + } + + if (server->ws_context) { + lws_context_destroy(server->ws_context); + } + + while (server->clients) { + struct client_session *next = server->clients->next; + free(server->clients); + server->clients = next; + } + + closelog(); +} |
