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(&current_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