Code

gidle: work around nested leave/enter problem
[ncmpc.git] / src / gidle.c
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);
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)
112         assert(source->id == 0);
114         source->callback(error, server_error, message,
115                          0, source->callback_ctx);
118 static void
119 mpd_glib_invoke_async_error(const struct mpd_glib_source *source)
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));
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)
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;
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)
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;
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)
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;
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)
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;
249 static gboolean
250 mpd_glib_source_callback(G_GNUC_UNUSED GIOChannel *_source,
251                          GIOCondition condition, gpointer data)
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;
305 static void
306 mpd_glib_add_watch(struct mpd_glib_source *source)
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;
321 void
322 mpd_glib_enter(struct mpd_glib_source *source)
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);
343 void
344 mpd_glib_leave(struct mpd_glib_source *source)
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;