configure.ac: require GLib 2.14
[ncmpc.git] / src / gidle.c
blobaf7457d85b2850b342b340b02d315e4419f90aca
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.
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 <errno.h>
39 struct mpd_glib_source {
40 struct mpd_connection *connection;
41 struct mpd_async *async;
42 struct mpd_parser *parser;
44 mpd_glib_callback_t callback;
45 void *callback_ctx;
47 GIOChannel *channel;
49 enum mpd_async_event io_events;
51 guint id;
53 enum mpd_idle idle_events;
55 /**
56 * This flag is a hack: it is set while mpd_glib_leave() is
57 * executed. mpd_glib_leave() might invoke the callback, and
58 * the callback might invoke mpd_glib_enter(), awkwardly
59 * leaving mpd_glib_leave() in idle mode. As long as this
60 * flag is set, mpd_glib_enter() is a no-op to prevent this.
62 bool leaving;
64 /**
65 * This flag is true when mpd_glib_free() has been called
66 * during a callback invoked from mpd_glib_leave().
67 * mpd_glib_leave() will do the real g_free() call then.
69 bool destroyed;
72 struct mpd_glib_source *
73 mpd_glib_new(struct mpd_connection *connection,
74 mpd_glib_callback_t callback, void *callback_ctx)
76 struct mpd_glib_source *source = g_new(struct mpd_glib_source, 1);
78 source->connection = connection;
79 source->async = mpd_connection_get_async(connection);
80 source->parser = mpd_parser_new();
81 /* XXX check source->parser!=NULL */
83 source->callback = callback;
84 source->callback_ctx = callback_ctx;
86 source->channel = g_io_channel_unix_new(mpd_async_get_fd(source->async));
87 source->io_events = 0;
88 source->id = 0;
89 source->leaving = false;
90 source->destroyed = false;
92 return source;
95 void
96 mpd_glib_free(struct mpd_glib_source *source)
98 assert(!source->destroyed);
100 if (source->id != 0)
101 g_source_remove(source->id);
103 g_io_channel_unref(source->channel);
105 mpd_parser_free(source->parser);
107 if (source->leaving)
108 source->destroyed = true;
109 else
110 g_free(source);
113 static void
114 mpd_glib_invoke(const struct mpd_glib_source *source)
116 assert(source->id == 0);
117 assert(!source->destroyed);
119 if (source->idle_events != 0)
120 source->callback(MPD_ERROR_SUCCESS, 0, NULL,
121 source->idle_events, source->callback_ctx);
124 static void
125 mpd_glib_invoke_error(const struct mpd_glib_source *source,
126 enum mpd_error error, enum mpd_server_error server_error,
127 const char *message)
129 assert(source->id == 0);
130 assert(!source->destroyed);
132 source->callback(error, server_error, message,
133 0, source->callback_ctx);
136 static void
137 mpd_glib_invoke_async_error(const struct mpd_glib_source *source)
139 assert(source->id == 0);
141 mpd_glib_invoke_error(source, mpd_async_get_error(source->async), 0,
142 mpd_async_get_error_message(source->async));
146 * Converts a GIOCondition bit mask to #mpd_async_event.
148 static enum mpd_async_event
149 g_io_condition_to_mpd_async_event(GIOCondition condition)
151 enum mpd_async_event events = 0;
153 if (condition & G_IO_IN)
154 events |= MPD_ASYNC_EVENT_READ;
156 if (condition & G_IO_OUT)
157 events |= MPD_ASYNC_EVENT_WRITE;
159 if (condition & G_IO_HUP)
160 events |= MPD_ASYNC_EVENT_HUP;
162 if (condition & G_IO_ERR)
163 events |= MPD_ASYNC_EVENT_ERROR;
165 return events;
169 * Converts a #mpd_async_event bit mask to GIOCondition.
171 static GIOCondition
172 mpd_async_events_to_g_io_condition(enum mpd_async_event events)
174 GIOCondition condition = 0;
176 if (events & MPD_ASYNC_EVENT_READ)
177 condition |= G_IO_IN;
179 if (events & MPD_ASYNC_EVENT_WRITE)
180 condition |= G_IO_OUT;
182 if (events & MPD_ASYNC_EVENT_HUP)
183 condition |= G_IO_HUP;
185 if (events & MPD_ASYNC_EVENT_ERROR)
186 condition |= G_IO_ERR;
188 return condition;
192 * Parses a response line from MPD.
194 * @return true on success, false on error
196 static bool
197 mpd_glib_feed(struct mpd_glib_source *source, char *line)
199 enum mpd_parser_result result;
201 result = mpd_parser_feed(source->parser, line);
202 switch (result) {
203 case MPD_PARSER_MALFORMED:
204 source->id = 0;
205 source->io_events = 0;
207 mpd_glib_invoke_error(source, MPD_ERROR_MALFORMED, 0,
208 "Malformed MPD response");
209 return false;
211 case MPD_PARSER_SUCCESS:
212 source->id = 0;
213 source->io_events = 0;
215 mpd_glib_invoke(source);
216 return false;
218 case MPD_PARSER_ERROR:
219 source->id = 0;
220 source->io_events = 0;
222 mpd_glib_invoke_error(source, MPD_ERROR_SERVER,
223 mpd_parser_get_server_error(source->parser),
224 mpd_parser_get_message(source->parser));
225 return false;
227 case MPD_PARSER_PAIR:
228 if (strcmp(mpd_parser_get_name(source->parser),
229 "changed") == 0)
230 source->idle_events |=
231 mpd_idle_name_parse(mpd_parser_get_value(source->parser));
233 break;
236 return true;
240 * Receives and evaluates a portion of the MPD response.
242 * @return true on success, false on error
244 static bool
245 mpd_glib_recv(struct mpd_glib_source *source)
247 char *line;
248 bool success;
250 while ((line = mpd_async_recv_line(source->async)) != NULL) {
251 success = mpd_glib_feed(source, line);
252 if (!success)
253 return false;
256 if (mpd_async_get_error(source->async) != MPD_ERROR_SUCCESS) {
257 source->id = 0;
258 source->io_events = 0;
260 mpd_glib_invoke_async_error(source);
261 return false;
264 return true;
267 static gboolean
268 mpd_glib_source_callback(G_GNUC_UNUSED GIOChannel *_source,
269 GIOCondition condition, gpointer data)
271 struct mpd_glib_source *source = data;
272 bool success;
273 enum mpd_async_event events;
275 assert(source->id != 0);
276 assert(source->io_events != 0);
278 /* let libmpdclient do some I/O */
280 success = mpd_async_io(source->async,
281 g_io_condition_to_mpd_async_event(condition));
282 if (!success) {
283 source->id = 0;
284 source->io_events = 0;
286 mpd_glib_invoke_async_error(source);
287 return false;
290 /* receive the response */
292 if ((condition & G_IO_IN) != 0) {
293 success = mpd_glib_recv(source);
294 if (!success)
295 return false;
298 /* continue polling? */
300 events = mpd_async_events(source->async);
301 if (events == 0) {
302 /* no events - disable watch */
303 source->id = 0;
304 source->io_events = 0;
306 return false;
307 } else if (events != source->io_events) {
308 /* different event mask: make new watch */
310 g_source_remove(source->id);
312 condition = mpd_async_events_to_g_io_condition(events);
313 source->id = g_io_add_watch(source->channel, condition,
314 mpd_glib_source_callback, source);
315 source->io_events = events;
317 return false;
318 } else
319 /* same event mask as before, enable the old watch */
320 return true;
323 static void
324 mpd_glib_add_watch(struct mpd_glib_source *source)
326 enum mpd_async_event events = mpd_async_events(source->async);
327 GIOCondition condition;
329 assert(source->io_events == 0);
330 assert(source->id == 0);
332 condition = mpd_async_events_to_g_io_condition(events);
334 source->id = g_io_add_watch(source->channel, condition,
335 mpd_glib_source_callback, source);
336 source->io_events = events;
339 bool
340 mpd_glib_enter(struct mpd_glib_source *source)
342 bool success;
344 assert(source->io_events == 0);
345 assert(source->id == 0);
346 assert(!source->destroyed);
348 if (source->leaving)
349 return false;
351 source->idle_events = 0;
353 success = mpd_async_send_command(source->async, "idle", NULL);
354 if (!success) {
355 mpd_glib_invoke_async_error(source);
356 return false;
359 mpd_glib_add_watch(source);
360 return true;
363 bool
364 mpd_glib_leave(struct mpd_glib_source *source)
366 enum mpd_idle events;
368 assert(!source->destroyed);
370 if (source->id == 0)
371 /* already left, callback was invoked */
372 return true;
374 g_source_remove(source->id);
375 source->id = 0;
376 source->io_events = 0;
378 events = source->idle_events == 0
379 ? mpd_run_noidle(source->connection)
380 : mpd_recv_idle(source->connection, false);
382 source->leaving = true;
384 if (events == 0 &&
385 mpd_connection_get_error(source->connection) != MPD_ERROR_SUCCESS) {
386 enum mpd_error error =
387 mpd_connection_get_error(source->connection);
388 enum mpd_server_error server_error =
389 error == MPD_ERROR_SERVER
390 ? mpd_connection_get_server_error(source->connection)
391 : 0;
393 mpd_glib_invoke_error(source, error, server_error,
394 mpd_connection_get_error_message(source->connection));
396 if (source->destroyed) {
397 g_free(source);
398 return false;
401 source->leaving = false;
402 return true;
405 source->idle_events |= events;
406 mpd_glib_invoke(source);
408 if (source->destroyed) {
409 g_free(source);
410 return false;
413 source->leaving = false;
414 return true;