95ac64621aea80b26070b9470e0415dbe88f01d1
1 /* ncmpc (Ncurses MPD Client)
2 (c) 2004-2010 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"
30 #include "Compiler.h"
32 #include <mpd/async.h>
33 #include <mpd/parser.h>
35 #include <glib.h>
37 #include <assert.h>
38 #include <string.h>
39 #include <errno.h>
41 struct mpd_glib_source {
42 struct mpd_connection *connection;
43 struct mpd_async *async;
44 struct mpd_parser *parser;
46 mpd_glib_callback_t callback;
47 void *callback_ctx;
49 GIOChannel *channel;
51 enum mpd_async_event io_events;
53 guint id;
55 enum mpd_idle idle_events;
57 /**
58 * This flag is a hack: it is set while mpd_glib_leave() is
59 * executed. mpd_glib_leave() might invoke the callback, and
60 * the callback might invoke mpd_glib_enter(), awkwardly
61 * leaving mpd_glib_leave() in idle mode. As long as this
62 * flag is set, mpd_glib_enter() is a no-op to prevent this.
63 */
64 bool leaving;
66 /**
67 * This flag is true when mpd_glib_free() has been called
68 * during a callback invoked from mpd_glib_leave().
69 * mpd_glib_leave() will do the real g_free() call then.
70 */
71 bool destroyed;
72 };
74 struct mpd_glib_source *
75 mpd_glib_new(struct mpd_connection *connection,
76 mpd_glib_callback_t callback, void *callback_ctx)
77 {
78 struct mpd_glib_source *source = g_new(struct mpd_glib_source, 1);
80 source->connection = connection;
81 source->async = mpd_connection_get_async(connection);
82 source->parser = mpd_parser_new();
83 /* XXX check source->parser!=NULL */
85 source->callback = callback;
86 source->callback_ctx = callback_ctx;
88 source->channel = g_io_channel_unix_new(mpd_async_get_fd(source->async));
89 source->io_events = 0;
90 source->id = 0;
91 source->leaving = false;
92 source->destroyed = false;
94 return source;
95 }
97 void
98 mpd_glib_free(struct mpd_glib_source *source)
99 {
100 assert(!source->destroyed);
102 if (source->id != 0)
103 g_source_remove(source->id);
105 g_io_channel_unref(source->channel);
107 mpd_parser_free(source->parser);
109 if (source->leaving)
110 source->destroyed = true;
111 else
112 g_free(source);
113 }
115 static void
116 mpd_glib_invoke(const struct mpd_glib_source *source)
117 {
118 assert(source->id == 0);
119 assert(!source->destroyed);
121 if (source->idle_events != 0)
122 source->callback(MPD_ERROR_SUCCESS, 0, NULL,
123 source->idle_events, source->callback_ctx);
124 }
126 static void
127 mpd_glib_invoke_error(const struct mpd_glib_source *source,
128 enum mpd_error error, enum mpd_server_error server_error,
129 const char *message)
130 {
131 assert(source->id == 0);
132 assert(!source->destroyed);
134 source->callback(error, server_error, message,
135 0, source->callback_ctx);
136 }
138 static void
139 mpd_glib_invoke_async_error(const struct mpd_glib_source *source)
140 {
141 assert(source->id == 0);
143 mpd_glib_invoke_error(source, mpd_async_get_error(source->async), 0,
144 mpd_async_get_error_message(source->async));
145 }
147 /**
148 * Converts a GIOCondition bit mask to #mpd_async_event.
149 */
150 static enum mpd_async_event
151 g_io_condition_to_mpd_async_event(GIOCondition condition)
152 {
153 enum mpd_async_event events = 0;
155 if (condition & G_IO_IN)
156 events |= MPD_ASYNC_EVENT_READ;
158 if (condition & G_IO_OUT)
159 events |= MPD_ASYNC_EVENT_WRITE;
161 if (condition & G_IO_HUP)
162 events |= MPD_ASYNC_EVENT_HUP;
164 if (condition & G_IO_ERR)
165 events |= MPD_ASYNC_EVENT_ERROR;
167 return events;
168 }
170 /**
171 * Converts a #mpd_async_event bit mask to GIOCondition.
172 */
173 static GIOCondition
174 mpd_async_events_to_g_io_condition(enum mpd_async_event events)
175 {
176 GIOCondition condition = 0;
178 if (events & MPD_ASYNC_EVENT_READ)
179 condition |= G_IO_IN;
181 if (events & MPD_ASYNC_EVENT_WRITE)
182 condition |= G_IO_OUT;
184 if (events & MPD_ASYNC_EVENT_HUP)
185 condition |= G_IO_HUP;
187 if (events & MPD_ASYNC_EVENT_ERROR)
188 condition |= G_IO_ERR;
190 return condition;
191 }
193 /**
194 * Parses a response line from MPD.
195 *
196 * @return true on success, false on error
197 */
198 static bool
199 mpd_glib_feed(struct mpd_glib_source *source, char *line)
200 {
201 enum mpd_parser_result result;
203 result = mpd_parser_feed(source->parser, line);
204 switch (result) {
205 case MPD_PARSER_MALFORMED:
206 source->id = 0;
207 source->io_events = 0;
209 mpd_glib_invoke_error(source, MPD_ERROR_MALFORMED, 0,
210 "Malformed MPD response");
211 return false;
213 case MPD_PARSER_SUCCESS:
214 source->id = 0;
215 source->io_events = 0;
217 mpd_glib_invoke(source);
218 return false;
220 case MPD_PARSER_ERROR:
221 source->id = 0;
222 source->io_events = 0;
224 mpd_glib_invoke_error(source, MPD_ERROR_SERVER,
225 mpd_parser_get_server_error(source->parser),
226 mpd_parser_get_message(source->parser));
227 return false;
229 case MPD_PARSER_PAIR:
230 if (strcmp(mpd_parser_get_name(source->parser),
231 "changed") == 0)
232 source->idle_events |=
233 mpd_idle_name_parse(mpd_parser_get_value(source->parser));
235 break;
236 }
238 return true;
239 }
241 /**
242 * Receives and evaluates a portion of the MPD response.
243 *
244 * @return true on success, false on error
245 */
246 static bool
247 mpd_glib_recv(struct mpd_glib_source *source)
248 {
249 char *line;
250 bool success;
252 while ((line = mpd_async_recv_line(source->async)) != NULL) {
253 success = mpd_glib_feed(source, line);
254 if (!success)
255 return false;
256 }
258 if (mpd_async_get_error(source->async) != MPD_ERROR_SUCCESS) {
259 source->id = 0;
260 source->io_events = 0;
262 mpd_glib_invoke_async_error(source);
263 return false;
264 }
266 return true;
267 }
269 static gboolean
270 mpd_glib_source_callback(gcc_unused GIOChannel *_source,
271 GIOCondition condition, gpointer data)
272 {
273 struct mpd_glib_source *source = data;
274 bool success;
275 enum mpd_async_event events;
277 assert(source->id != 0);
278 assert(source->io_events != 0);
280 /* let libmpdclient do some I/O */
282 success = mpd_async_io(source->async,
283 g_io_condition_to_mpd_async_event(condition));
284 if (!success) {
285 source->id = 0;
286 source->io_events = 0;
288 mpd_glib_invoke_async_error(source);
289 return false;
290 }
292 /* receive the response */
294 if ((condition & G_IO_IN) != 0) {
295 success = mpd_glib_recv(source);
296 if (!success)
297 return false;
298 }
300 /* continue polling? */
302 events = mpd_async_events(source->async);
303 if (events == 0) {
304 /* no events - disable watch */
305 source->id = 0;
306 source->io_events = 0;
308 return false;
309 } else if (events != source->io_events) {
310 /* different event mask: make new watch */
312 g_source_remove(source->id);
314 condition = mpd_async_events_to_g_io_condition(events);
315 source->id = g_io_add_watch(source->channel, condition,
316 mpd_glib_source_callback, source);
317 source->io_events = events;
319 return false;
320 } else
321 /* same event mask as before, enable the old watch */
322 return true;
323 }
325 static void
326 mpd_glib_add_watch(struct mpd_glib_source *source)
327 {
328 enum mpd_async_event events = mpd_async_events(source->async);
329 GIOCondition condition;
331 assert(source->io_events == 0);
332 assert(source->id == 0);
334 condition = mpd_async_events_to_g_io_condition(events);
336 source->id = g_io_add_watch(source->channel, condition,
337 mpd_glib_source_callback, source);
338 source->io_events = events;
339 }
341 bool
342 mpd_glib_enter(struct mpd_glib_source *source)
343 {
344 bool success;
346 assert(source->io_events == 0);
347 assert(source->id == 0);
348 assert(!source->destroyed);
350 if (source->leaving)
351 return false;
353 source->idle_events = 0;
355 success = mpd_async_send_command(source->async, "idle", NULL);
356 if (!success) {
357 mpd_glib_invoke_async_error(source);
358 return false;
359 }
361 mpd_glib_add_watch(source);
362 return true;
363 }
365 bool
366 mpd_glib_leave(struct mpd_glib_source *source)
367 {
368 enum mpd_idle events;
370 assert(!source->destroyed);
372 if (source->id == 0)
373 /* already left, callback was invoked */
374 return true;
376 g_source_remove(source->id);
377 source->id = 0;
378 source->io_events = 0;
380 events = source->idle_events == 0
381 ? mpd_run_noidle(source->connection)
382 : mpd_recv_idle(source->connection, false);
384 source->leaving = true;
386 if (events == 0 &&
387 mpd_connection_get_error(source->connection) != MPD_ERROR_SUCCESS) {
388 enum mpd_error error =
389 mpd_connection_get_error(source->connection);
390 enum mpd_server_error server_error =
391 error == MPD_ERROR_SERVER
392 ? mpd_connection_get_server_error(source->connection)
393 : 0;
395 mpd_glib_invoke_error(source, error, server_error,
396 mpd_connection_get_error_message(source->connection));
398 if (source->destroyed) {
399 g_free(source);
400 return false;
401 }
403 source->leaving = false;
404 return true;
405 }
407 source->idle_events |= events;
408 mpd_glib_invoke(source);
410 if (source->destroyed) {
411 g_free(source);
412 return false;
413 }
415 source->leaving = false;
416 return true;
417 }