1 /* mpdscribble (MPD Client)
2 * Copyright (C) 2008-2010 The Music Player Daemon Project
3 * Copyright (C) 2005-2008 Kuno Woudt <kuno@frob.nl>
4 * Project homepage: http://musicpd.org
5
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 #include "lmc.h"
22 #include "file.h"
23 #include "compat.h"
24
25 #if LIBMPDCLIENT_CHECK_VERSION(2,5,0)
26 #include <mpd/message.h>
27 #endif
28
29 #include <glib.h>
30
31 #include <assert.h>
32 #include <stdbool.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37
38 static struct mpd_connection *g_mpd;
39 static bool idle_supported, idle_notified;
40 static unsigned last_id = -1;
41 static struct mpd_song *current_song;
42 static bool was_paused;
43
44 /**
45 * Is the current song being "loved"? That variable gets set when the
46 * client-to-client command "love" is received.
47 */
48 static bool love;
49
50 #if LIBMPDCLIENT_CHECK_VERSION(2,5,0)
51 static bool subscribed;
52 #endif
53
54 static char *g_host;
55 static int g_port;
56
57 static guint reconnect_source_id, update_source_id, idle_source_id;
58
59 static void
60 lmc_schedule_reconnect(void);
61
62 static void
63 lmc_schedule_update(void);
64
65 static void
66 lmc_schedule_idle(void);
67
lmc_failure(void)68 static void lmc_failure(void)
69 {
70 char *msg = g_strescape(mpd_connection_get_error_message(g_mpd), NULL);
71
72 g_warning("mpd error (%u): %s\n",
73 mpd_connection_get_error(g_mpd), msg);
74 g_free(msg);
75 mpd_connection_free(g_mpd);
76 g_mpd = NULL;
77 }
78
79 #if LIBMPDCLIENT_CHECK_VERSION(2,4,0)
80
81 static char *
settings_name(const struct mpd_settings * settings)82 settings_name(const struct mpd_settings *settings)
83 {
84 const char *host = mpd_settings_get_host(settings);
85 if (host == NULL)
86 host = "unknown";
87
88 if (host[0] == '/')
89 return g_strdup(host);
90
91 unsigned port = mpd_settings_get_port(settings);
92 if (port == 0 || port == 6600)
93 return g_strdup(host);
94
95 return g_strdup_printf("%s:%u", host, port);
96 }
97
98 #endif
99
100 static char *
connection_settings_name(const struct mpd_connection * connection)101 connection_settings_name(const struct mpd_connection *connection)
102 {
103 #if LIBMPDCLIENT_CHECK_VERSION(2,4,0)
104 const struct mpd_settings *settings =
105 mpd_connection_get_settings(connection);
106 if (settings == NULL)
107 return g_strdup("unknown");
108
109 return settings_name(settings);
110 #else
111 (void)connection;
112 return g_strdup(g_host);
113 #endif
114 }
115
116 static gboolean
lmc_reconnect(G_GNUC_UNUSED gpointer data)117 lmc_reconnect(G_GNUC_UNUSED gpointer data)
118 {
119 const unsigned *version;
120
121 g_mpd = mpd_connection_new(g_host, g_port, 0);
122 if (mpd_connection_get_error(g_mpd) != MPD_ERROR_SUCCESS) {
123 lmc_failure();
124 return true;
125 }
126
127 idle_supported = mpd_connection_cmp_server_version(g_mpd, 0, 14, 0) >= 0;
128
129 version = mpd_connection_get_server_version(g_mpd);
130 char *name = connection_settings_name(g_mpd);
131 g_message("connected to mpd %i.%i.%i at %s\n",
132 version[0], version[1], version[2],
133 name);
134 g_free(name);
135
136 #if LIBMPDCLIENT_CHECK_VERSION(2,5,0)
137 subscribed = mpd_run_subscribe(g_mpd, "mpdscribble");
138 if (!subscribed && !mpd_connection_clear_error(g_mpd)) {
139 lmc_failure();
140 return true;
141 }
142 #endif
143
144 lmc_schedule_update();
145
146 reconnect_source_id = 0;
147 return false;
148 }
149
150 static void
lmc_schedule_reconnect(void)151 lmc_schedule_reconnect(void)
152 {
153 assert(reconnect_source_id == 0);
154
155 g_message("waiting 15 seconds before reconnecting\n");
156
157 reconnect_source_id = g_timeout_add_seconds(15, lmc_reconnect, NULL);
158 }
159
lmc_connect(char * host,int port)160 void lmc_connect(char *host, int port)
161 {
162 g_host = host;
163 g_port = port;
164
165 if (lmc_reconnect(NULL))
166 lmc_schedule_reconnect();
167 }
168
lmc_disconnect(void)169 void lmc_disconnect(void)
170 {
171 if (reconnect_source_id != 0)
172 g_source_remove(reconnect_source_id);
173
174 if (update_source_id != 0)
175 g_source_remove(update_source_id);
176
177 if (idle_source_id != 0)
178 g_source_remove(idle_source_id);
179
180 if (g_mpd) {
181 mpd_connection_free(g_mpd);
182 g_mpd = NULL;
183 }
184
185 if (current_song != NULL) {
186 mpd_song_free(current_song);
187 current_song = NULL;
188 }
189 }
190
191 static enum mpd_state
lmc_current(struct mpd_song ** song_r,unsigned * elapsed_r)192 lmc_current(struct mpd_song **song_r, unsigned *elapsed_r)
193 {
194 struct mpd_status *status;
195 enum mpd_state state;
196 struct mpd_song *song;
197
198 assert(g_mpd != NULL);
199
200 mpd_command_list_begin(g_mpd, true);
201 mpd_send_status(g_mpd);
202 mpd_send_current_song(g_mpd);
203 mpd_command_list_end(g_mpd);
204
205 status = mpd_recv_status(g_mpd);
206 if (!status) {
207 lmc_failure();
208 return MPD_STATE_UNKNOWN;
209 }
210
211 state = mpd_status_get_state(status);
212 *elapsed_r = mpd_status_get_elapsed_time(status);
213
214 mpd_status_free(status);
215
216 if (state != MPD_STATE_PLAY) {
217 if (!mpd_response_finish(g_mpd)) {
218 lmc_failure();
219 return MPD_STATE_UNKNOWN;
220 }
221
222 return state;
223 }
224
225 if (!mpd_response_next(g_mpd)) {
226 lmc_failure();
227 return MPD_STATE_UNKNOWN;
228 }
229
230 song = mpd_recv_song(g_mpd);
231 if (song == NULL) {
232 if (!mpd_response_finish(g_mpd)) {
233 lmc_failure();
234 return MPD_STATE_UNKNOWN;
235 }
236
237 return MPD_STATE_UNKNOWN;
238 }
239
240 if (!mpd_response_finish(g_mpd)) {
241 mpd_song_free(song);
242 lmc_failure();
243 return MPD_STATE_UNKNOWN;
244 }
245
246 *song_r = song;
247 return MPD_STATE_PLAY;
248 }
249
250 /**
251 * Update: determine MPD's current song and enqueue submissions.
252 */
253 static gboolean
lmc_update(G_GNUC_UNUSED gpointer data)254 lmc_update(G_GNUC_UNUSED gpointer data)
255 {
256 struct mpd_song *prev;
257 enum mpd_state state;
258 unsigned elapsed = 0;
259
260 prev = current_song;
261 state = lmc_current(¤t_song, &elapsed);
262
263 if (state == MPD_STATE_PAUSE) {
264 if (!was_paused)
265 song_paused();
266 was_paused = true;
267
268 if (idle_supported) {
269 lmc_schedule_idle();
270 update_source_id = 0;
271 return false;
272 }
273
274 return true;
275 } else if (state != MPD_STATE_PLAY) {
276 current_song = NULL;
277 last_id = -1;
278 was_paused = false;
279 } else if (mpd_song_get_tag(current_song, MPD_TAG_ARTIST, 0) == NULL ||
280 mpd_song_get_tag(current_song, MPD_TAG_TITLE, 0) == NULL) {
281 if (mpd_song_get_id(current_song) != last_id) {
282 g_message("new song detected with tags missing (%s)\n",
283 mpd_song_get_uri(current_song));
284 last_id = mpd_song_get_id(current_song);
285 }
286
287 mpd_song_free(current_song);
288 current_song = NULL;
289 }
290
291 if (was_paused) {
292 if (current_song != NULL &&
293 mpd_song_get_id(current_song) == last_id)
294 song_continued();
295 was_paused = false;
296 }
297
298 /* submit the previous song */
299 if (prev != NULL &&
300 (current_song == NULL ||
301 mpd_song_get_id(prev) != mpd_song_get_id(current_song))) {
302 song_ended(prev, love);
303 love = false;
304 }
305
306 if (current_song != NULL) {
307 if (mpd_song_get_id(current_song) != last_id) {
308 /* new song. */
309
310 song_started(current_song);
311 last_id = mpd_song_get_id(current_song);
312 } else {
313 /* still playing the previous song */
314
315 song_playing(current_song, elapsed);
316 }
317 }
318
319 if (prev != NULL)
320 mpd_song_free(prev);
321
322 if (g_mpd == NULL) {
323 lmc_schedule_reconnect();
324 update_source_id = 0;
325 return false;
326 }
327
328 if (idle_supported) {
329 lmc_schedule_idle();
330 update_source_id = 0;
331 return false;
332 }
333
334 return true;
335 }
336
337 static void
lmc_schedule_update(void)338 lmc_schedule_update(void)
339 {
340 assert(update_source_id == 0);
341
342 update_source_id = g_timeout_add_seconds(idle_supported ? 0 : file_config.sleep,
343 lmc_update, NULL);
344 }
345
346 #if LIBMPDCLIENT_CHECK_VERSION(2,5,0)
347
348 static bool
lmc_read_messages(void)349 lmc_read_messages(void)
350 {
351 assert(subscribed);
352
353 if (!mpd_send_read_messages(g_mpd))
354 return mpd_connection_clear_error(g_mpd);
355
356 struct mpd_message *msg;
357 while ((msg = mpd_recv_message(g_mpd)) != NULL) {
358 const char *text = mpd_message_get_text(msg);
359 if (strcmp(text, "love") == 0)
360 love = true;
361 else
362 g_message("Unrecognized client-to-client message: '%s'",
363 text);
364
365 mpd_message_free(msg);
366 }
367
368 return mpd_response_finish(g_mpd);
369 }
370
371 #endif
372
373 static gboolean
lmc_idle(G_GNUC_UNUSED GIOChannel * source,G_GNUC_UNUSED GIOCondition condition,G_GNUC_UNUSED gpointer data)374 lmc_idle(G_GNUC_UNUSED GIOChannel *source,
375 G_GNUC_UNUSED GIOCondition condition,
376 G_GNUC_UNUSED gpointer data)
377 {
378 bool success;
379 enum mpd_idle idle;
380
381 assert(idle_source_id != 0);
382 assert(g_mpd != NULL);
383 assert(mpd_connection_get_error(g_mpd) == MPD_ERROR_SUCCESS);
384
385 idle_source_id = 0;
386
387 idle = mpd_recv_idle(g_mpd, false);
388 success = mpd_response_finish(g_mpd);
389
390 if (!success && mpd_connection_get_error(g_mpd) == MPD_ERROR_SERVER &&
391 mpd_connection_get_server_error(g_mpd) == MPD_SERVER_ERROR_UNKNOWN_CMD &&
392 mpd_connection_clear_error(g_mpd)) {
393 /* MPD does not recognize the "idle" command - disable
394 it for this connection */
395
396 g_message("MPD does not support the 'idle' command - "
397 "falling back to polling\n");
398
399 idle_supported = false;
400 lmc_schedule_update();
401 return false;
402 }
403
404 if (!success) {
405 lmc_failure();
406 lmc_schedule_reconnect();
407 return false;
408 }
409
410 #if LIBMPDCLIENT_CHECK_VERSION(2,5,0)
411 if (subscribed && (idle & MPD_IDLE_MESSAGE) != 0 &&
412 !lmc_read_messages()) {
413 lmc_failure();
414 lmc_schedule_reconnect();
415 return false;
416 }
417 #endif
418
419 if (idle & MPD_IDLE_PLAYER)
420 /* there was a change: query MPD */
421 lmc_schedule_update();
422 else
423 /* nothing interesting: re-enter idle */
424 lmc_schedule_idle();
425
426 return false;
427 }
428
429 static void
lmc_schedule_idle(void)430 lmc_schedule_idle(void)
431 {
432 GIOChannel *channel;
433
434 assert(idle_source_id == 0);
435 assert(g_mpd != NULL);
436
437 idle_notified = false;
438
439 enum mpd_idle mask = MPD_IDLE_PLAYER;
440 #if LIBMPDCLIENT_CHECK_VERSION(2,5,0)
441 if (subscribed)
442 mask |= MPD_IDLE_MESSAGE;
443 #endif
444
445 if (!mpd_send_idle_mask(g_mpd, mask)) {
446 lmc_failure();
447 lmc_schedule_reconnect();
448 return;
449 }
450
451 /* add a GLib watch on the libmpdclient socket */
452
453 channel = g_io_channel_unix_new(mpd_connection_get_fd(g_mpd));
454 idle_source_id = g_io_add_watch(channel, G_IO_IN, lmc_idle, NULL);
455 g_io_channel_unref(channel);
456 }
457