Code

Merge branch 'master' of git://git.musicpd.org/avuton/ncmpc
[ncmpc.git] / src / gidle.c
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"
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;
65         /**
66          * This flag is true when mpd_glib_free() has been called
67          * during a callback invoked from mpd_glib_leave().
68          * mpd_glib_leave() will do the real g_free() call then.
69          */
70         bool destroyed;
71 };
73 struct mpd_glib_source *
74 mpd_glib_new(struct mpd_connection *connection,
75              mpd_glib_callback_t callback, void *callback_ctx)
76 {
77         struct mpd_glib_source *source = g_new(struct mpd_glib_source, 1);
79         source->connection = connection;
80         source->async = mpd_connection_get_async(connection);
81         source->parser = mpd_parser_new();
82         /* XXX check source->parser!=NULL */
84         source->callback = callback;
85         source->callback_ctx = callback_ctx;
87         source->channel = g_io_channel_unix_new(mpd_async_get_fd(source->async));
88         source->io_events = 0;
89         source->id = 0;
90         source->leaving = false;
91         source->destroyed = false;
93         return source;
94 }
96 void
97 mpd_glib_free(struct mpd_glib_source *source)
98 {
99         assert(!source->destroyed);
101         if (source->id != 0)
102                 g_source_remove(source->id);
104         g_io_channel_unref(source->channel);
106         mpd_parser_free(source->parser);
108         if (source->leaving)
109                 source->destroyed = true;
110         else
111                 g_free(source);
114 static void
115 mpd_glib_invoke(const struct mpd_glib_source *source)
117         assert(source->id == 0);
118         assert(!source->destroyed);
120         if (source->idle_events != 0)
121                 source->callback(MPD_ERROR_SUCCESS, 0, NULL,
122                                  source->idle_events, source->callback_ctx);
125 static void
126 mpd_glib_invoke_error(const struct mpd_glib_source *source,
127                       enum mpd_error error, enum mpd_server_error server_error,
128                       const char *message)
130         assert(source->id == 0);
131         assert(!source->destroyed);
133         source->callback(error, server_error, message,
134                          0, source->callback_ctx);
137 static void
138 mpd_glib_invoke_async_error(const struct mpd_glib_source *source)
140         assert(source->id == 0);
142         mpd_glib_invoke_error(source, mpd_async_get_error(source->async), 0,
143                               mpd_async_get_error_message(source->async));
146 /**
147  * Converts a GIOCondition bit mask to #mpd_async_event.
148  */
149 static enum mpd_async_event
150 g_io_condition_to_mpd_async_event(GIOCondition condition)
152         enum mpd_async_event events = 0;
154         if (condition & G_IO_IN)
155                 events |= MPD_ASYNC_EVENT_READ;
157         if (condition & G_IO_OUT)
158                 events |= MPD_ASYNC_EVENT_WRITE;
160         if (condition & G_IO_HUP)
161                 events |= MPD_ASYNC_EVENT_HUP;
163         if (condition & G_IO_ERR)
164                 events |= MPD_ASYNC_EVENT_ERROR;
166         return events;
169 /**
170  * Converts a #mpd_async_event bit mask to GIOCondition.
171  */
172 static GIOCondition
173 mpd_async_events_to_g_io_condition(enum mpd_async_event events)
175         GIOCondition condition = 0;
177         if (events & MPD_ASYNC_EVENT_READ)
178                 condition |= G_IO_IN;
180         if (events & MPD_ASYNC_EVENT_WRITE)
181                 condition |= G_IO_OUT;
183         if (events & MPD_ASYNC_EVENT_HUP)
184                 condition |= G_IO_HUP;
186         if (events & MPD_ASYNC_EVENT_ERROR)
187                 condition |= G_IO_ERR;
189         return condition;
192 /**
193  * Parses a response line from MPD.
194  *
195  * @return true on success, false on error
196  */
197 static bool
198 mpd_glib_feed(struct mpd_glib_source *source, char *line)
200         enum mpd_parser_result result;
202         result = mpd_parser_feed(source->parser, line);
203         switch (result) {
204         case MPD_PARSER_MALFORMED:
205                 source->id = 0;
206                 source->io_events = 0;
208                 mpd_glib_invoke_error(source, MPD_ERROR_MALFORMED, 0,
209                                       "Malformed MPD response");
210                 return false;
212         case MPD_PARSER_SUCCESS:
213                 source->id = 0;
214                 source->io_events = 0;
216                 mpd_glib_invoke(source);
217                 return false;
219         case MPD_PARSER_ERROR:
220                 source->id = 0;
221                 source->io_events = 0;
223                 mpd_glib_invoke_error(source, MPD_ERROR_SERVER,
224                                       mpd_parser_get_server_error(source->parser),
225                                       mpd_parser_get_message(source->parser));
226                 return false;
228         case MPD_PARSER_PAIR:
229                 if (strcmp(mpd_parser_get_name(source->parser),
230                            "changed") == 0)
231                         source->idle_events |=
232                                 mpd_idle_name_parse(mpd_parser_get_value(source->parser));
234                 break;
235         }
237         return true;
240 /**
241  * Receives and evaluates a portion of the MPD response.
242  *
243  * @return true on success, false on error
244  */
245 static bool
246 mpd_glib_recv(struct mpd_glib_source *source)
248         char *line;
249         bool success;
251         while ((line = mpd_async_recv_line(source->async)) != NULL) {
252                 success = mpd_glib_feed(source, line);
253                 if (!success)
254                         return false;
255         }
257         if (mpd_async_get_error(source->async) != MPD_ERROR_SUCCESS) {
258                 source->id = 0;
259                 source->io_events = 0;
261                 mpd_glib_invoke_async_error(source);
262                 return false;
263         }
265         return true;
268 static gboolean
269 mpd_glib_source_callback(G_GNUC_UNUSED GIOChannel *_source,
270                          GIOCondition condition, gpointer data)
272         struct mpd_glib_source *source = data;
273         bool success;
274         enum mpd_async_event events;
276         assert(source->id != 0);
277         assert(source->io_events != 0);
279         /* let libmpdclient do some I/O */
281         success = mpd_async_io(source->async,
282                                g_io_condition_to_mpd_async_event(condition));
283         if (!success) {
284                 source->id = 0;
285                 source->io_events = 0;
287                 mpd_glib_invoke_async_error(source);
288                 return false;
289         }
291         /* receive the response */
293         if ((condition & G_IO_IN) != 0) {
294                 success = mpd_glib_recv(source);
295                 if (!success)
296                         return false;
297         }
299         /* continue polling? */
301         events = mpd_async_events(source->async);
302         if (events == 0) {
303                 /* no events - disable watch */
304                 source->id = 0;
305                 source->io_events = 0;
307                 return false;
308         } else if (events != source->io_events) {
309                 /* different event mask: make new watch */
311                 g_source_remove(source->id);
313                 condition = mpd_async_events_to_g_io_condition(events);
314                 source->id = g_io_add_watch(source->channel, condition,
315                                             mpd_glib_source_callback, source);
316                 source->io_events = events;
318                 return false;
319         } else
320                 /* same event mask as before, enable the old watch */
321                 return true;
324 static void
325 mpd_glib_add_watch(struct mpd_glib_source *source)
327         enum mpd_async_event events = mpd_async_events(source->async);
328         GIOCondition condition;
330         assert(source->io_events == 0);
331         assert(source->id == 0);
333         condition = mpd_async_events_to_g_io_condition(events);
335         source->id = g_io_add_watch(source->channel, condition,
336                                     mpd_glib_source_callback, source);
337         source->io_events = events;
340 bool
341 mpd_glib_enter(struct mpd_glib_source *source)
343         bool success;
345         assert(source->io_events == 0);
346         assert(source->id == 0);
347         assert(!source->destroyed);
349         if (source->leaving)
350                 return false;
352         source->idle_events = 0;
354         success = mpd_async_send_command(source->async, "idle", NULL);
355         if (!success) {
356                 mpd_glib_invoke_async_error(source);
357                 return false;
358         }
360         mpd_glib_add_watch(source);
361         return true;
364 bool
365 mpd_glib_leave(struct mpd_glib_source *source)
367         enum mpd_idle events;
369         assert(!source->destroyed);
371         if (source->id == 0)
372                 /* already left, callback was invoked */
373                 return true;
375         g_source_remove(source->id);
376         source->id = 0;
377         source->io_events = 0;
379         events = source->idle_events == 0
380                 ? mpd_run_noidle(source->connection)
381                 : mpd_recv_idle(source->connection, false);
383         source->leaving = true;
385         if (events == 0 &&
386             mpd_connection_get_error(source->connection) != MPD_ERROR_SUCCESS) {
387                 enum mpd_error error =
388                         mpd_connection_get_error(source->connection);
389                 enum mpd_server_error server_error =
390                         error == MPD_ERROR_SERVER
391                         ? mpd_connection_get_server_error(source->connection)
392                         : 0;
394                 mpd_glib_invoke_error(source, error, server_error,
395                                       mpd_connection_get_error_message(source->connection));
397                 if (source->destroyed) {
398                         g_free(source);
399                         return false;
400                 }
402                 source->leaving = false;
403                 return true;
404         }
406         source->idle_events |= events;
407         mpd_glib_invoke(source);
409         if (source->destroyed) {
410                 g_free(source);
411                 return false;
412         }
414         source->leaving = false;
415         return true;