1 /* ncmpc (Ncurses MPD Client)
2 (c) 2004-2009 The Music Player Daemon Project
3 Project homepage: http://musicpd.org
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
7 are met:
9 - Redistributions of source code must retain the above copyright
10 notice, this list of conditions and the following disclaimer.
12 - Redistributions in binary form must reproduce the above copyright
13 notice, this list of conditions and the following disclaimer in the
14 documentation and/or other materials provided with the distribution.
16 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
20 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
29 #include "gidle.h"
31 #include <mpd/async.h>
32 #include <mpd/parser.h>
34 #include <glib.h>
35 #include <assert.h>
36 #include <string.h>
37 #include <sys/select.h>
38 #include <errno.h>
40 struct mpd_glib_source {
41 struct mpd_connection *connection;
42 struct mpd_async *async;
43 struct mpd_parser *parser;
45 mpd_glib_callback_t callback;
46 void *callback_ctx;
48 GIOChannel *channel;
50 enum mpd_async_event io_events;
52 guint id;
54 enum mpd_idle idle_events;
56 /**
57 * This flag is a hack: it is set while mpd_glib_leave() is
58 * executed. mpd_glib_leave() might invoke the callback, and
59 * the callback might invoke mpd_glib_enter(), awkwardly
60 * leaving mpd_glib_leave() in idle mode. As long as this
61 * flag is set, mpd_glib_enter() is a no-op to prevent this.
62 */
63 bool leaving;
64 };
66 struct mpd_glib_source *
67 mpd_glib_new(struct mpd_connection *connection,
68 mpd_glib_callback_t callback, void *callback_ctx)
69 {
70 struct mpd_glib_source *source = g_new(struct mpd_glib_source, 1);
72 source->connection = connection;
73 source->async = mpd_connection_get_async(connection);
74 source->parser = mpd_parser_new();
75 /* XXX check source->parser!=NULL */
77 source->callback = callback;
78 source->callback_ctx = callback_ctx;
80 source->channel = g_io_channel_unix_new(mpd_async_get_fd(source->async));
81 source->io_events = 0;
82 source->id = 0;
83 source->leaving = false;
85 return source;
86 }
88 void
89 mpd_glib_free(struct mpd_glib_source *source)
90 {
91 if (source->id != 0)
92 g_source_remove(source->id);
94 g_io_channel_unref(source->channel);
95 }
97 static void
98 mpd_glib_invoke(const struct mpd_glib_source *source)
99 {
100 assert(source->id == 0);
102 if (source->idle_events != 0)
103 source->callback(MPD_ERROR_SUCCESS, 0, NULL,
104 source->idle_events, source->callback_ctx);
105 }
107 static void
108 mpd_glib_invoke_error(const struct mpd_glib_source *source,
109 enum mpd_error error, enum mpd_server_error server_error,
110 const char *message)
111 {
112 assert(source->id == 0);
114 source->callback(error, server_error, message,
115 0, source->callback_ctx);
116 }
118 static void
119 mpd_glib_invoke_async_error(const struct mpd_glib_source *source)
120 {
121 assert(source->id == 0);
123 mpd_glib_invoke_error(source, mpd_async_get_error(source->async), 0,
124 mpd_async_get_error_message(source->async));
125 }
127 /**
128 * Converts a GIOCondition bit mask to #mpd_async_event.
129 */
130 static enum mpd_async_event
131 g_io_condition_to_mpd_async_event(GIOCondition condition)
132 {
133 enum mpd_async_event events = 0;
135 if (condition & G_IO_IN)
136 events |= MPD_ASYNC_EVENT_READ;
138 if (condition & G_IO_OUT)
139 events |= MPD_ASYNC_EVENT_WRITE;
141 if (condition & G_IO_HUP)
142 events |= MPD_ASYNC_EVENT_HUP;
144 if (condition & G_IO_ERR)
145 events |= MPD_ASYNC_EVENT_ERROR;
147 return events;
148 }
150 /**
151 * Converts a #mpd_async_event bit mask to GIOCondition.
152 */
153 static GIOCondition
154 mpd_async_events_to_g_io_condition(enum mpd_async_event events)
155 {
156 GIOCondition condition = 0;
158 if (events & MPD_ASYNC_EVENT_READ)
159 condition |= G_IO_IN;
161 if (events & MPD_ASYNC_EVENT_WRITE)
162 condition |= G_IO_OUT;
164 if (events & MPD_ASYNC_EVENT_HUP)
165 condition |= G_IO_HUP;
167 if (events & MPD_ASYNC_EVENT_ERROR)
168 condition |= G_IO_ERR;
170 return condition;
171 }
173 /**
174 * Parses a response line from MPD.
175 *
176 * @return true on success, false on error
177 */
178 static bool
179 mpd_glib_feed(struct mpd_glib_source *source, char *line)
180 {
181 enum mpd_parser_result result;
183 result = mpd_parser_feed(source->parser, line);
184 switch (result) {
185 case MPD_PARSER_MALFORMED:
186 source->id = 0;
187 source->io_events = 0;
189 mpd_glib_invoke_error(source, MPD_ERROR_MALFORMED, 0,
190 "Malformed MPD response");
191 return false;
193 case MPD_PARSER_SUCCESS:
194 source->id = 0;
195 source->io_events = 0;
197 mpd_glib_invoke(source);
198 return false;
200 case MPD_PARSER_ERROR:
201 source->id = 0;
202 source->io_events = 0;
204 mpd_glib_invoke_error(source, MPD_ERROR_SERVER,
205 mpd_parser_get_server_error(source->parser),
206 mpd_parser_get_message(source->parser));
207 return false;
209 case MPD_PARSER_PAIR:
210 if (strcmp(mpd_parser_get_name(source->parser),
211 "changed") == 0)
212 source->idle_events |=
213 mpd_idle_name_parse(mpd_parser_get_value(source->parser));
215 break;
216 }
218 return true;
219 }
221 /**
222 * Receives and evaluates a portion of the MPD response.
223 *
224 * @return true on success, false on error
225 */
226 static bool
227 mpd_glib_recv(struct mpd_glib_source *source)
228 {
229 char *line;
230 bool success;
232 while ((line = mpd_async_recv_line(source->async)) != NULL) {
233 success = mpd_glib_feed(source, line);
234 if (!success)
235 return false;
236 }
238 if (mpd_async_get_error(source->async) != MPD_ERROR_SUCCESS) {
239 source->id = 0;
240 source->io_events = 0;
242 mpd_glib_invoke_async_error(source);
243 return false;
244 }
246 return true;
247 }
249 static gboolean
250 mpd_glib_source_callback(G_GNUC_UNUSED GIOChannel *_source,
251 GIOCondition condition, gpointer data)
252 {
253 struct mpd_glib_source *source = data;
254 bool success;
255 enum mpd_async_event events;
257 assert(source->id != 0);
258 assert(source->io_events != 0);
260 /* let libmpdclient do some I/O */
262 success = mpd_async_io(source->async,
263 g_io_condition_to_mpd_async_event(condition));
264 if (!success) {
265 source->id = 0;
266 source->io_events = 0;
268 mpd_glib_invoke_async_error(source);
269 return false;
270 }
272 /* receive the response */
274 if ((condition & G_IO_IN) != 0) {
275 success = mpd_glib_recv(source);
276 if (!success)
277 return false;
278 }
280 /* continue polling? */
282 events = mpd_async_events(source->async);
283 if (events == 0) {
284 /* no events - disable watch */
285 source->id = 0;
286 source->io_events = 0;
288 return false;
289 } else if (events != source->io_events) {
290 /* different event mask: make new watch */
292 g_source_remove(source->id);
294 condition = mpd_async_events_to_g_io_condition(events);
295 source->id = g_io_add_watch(source->channel, condition,
296 mpd_glib_source_callback, source);
297 source->io_events = events;
299 return false;
300 } else
301 /* same event mask as before, enable the old watch */
302 return true;
303 }
305 static void
306 mpd_glib_add_watch(struct mpd_glib_source *source)
307 {
308 enum mpd_async_event events = mpd_async_events(source->async);
309 GIOCondition condition;
311 assert(source->io_events == 0);
312 assert(source->id == 0);
314 condition = mpd_async_events_to_g_io_condition(events);
316 source->id = g_io_add_watch(source->channel, condition,
317 mpd_glib_source_callback, source);
318 source->io_events = events;
319 }
321 void
322 mpd_glib_enter(struct mpd_glib_source *source)
323 {
324 bool success;
326 assert(source->io_events == 0);
327 assert(source->id == 0);
329 if (source->leaving)
330 return;
332 source->idle_events = 0;
334 success = mpd_async_send_command(source->async, "idle", NULL);
335 if (!success) {
336 mpd_glib_invoke_async_error(source);
337 return;
338 }
340 mpd_glib_add_watch(source);
341 }
343 void
344 mpd_glib_leave(struct mpd_glib_source *source)
345 {
346 enum mpd_idle events;
348 if (source->id == 0)
349 /* already left, callback was invoked */
350 return;
352 g_source_remove(source->id);
353 source->id = 0;
354 source->io_events = 0;
356 events = source->idle_events == 0
357 ? mpd_run_noidle(source->connection)
358 : mpd_recv_idle(source->connection, false);
360 source->leaving = true;
362 if (events == 0 &&
363 mpd_connection_get_error(source->connection) != MPD_ERROR_SUCCESS) {
364 enum mpd_error error =
365 mpd_connection_get_error(source->connection);
366 enum mpd_server_error server_error =
367 error == MPD_ERROR_SERVER
368 ? mpd_connection_get_server_error(source->connection)
369 : 0;
371 mpd_glib_invoke_error(source, error, server_error,
372 mpd_connection_get_error_message(source->connection));
373 source->leaving = false;
374 return;
375 }
377 source->idle_events |= events;
378 mpd_glib_invoke(source);
380 source->leaving = false;
381 }