Code

*: use Compiler.h macros instead of glib.h
[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"
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);
115 static void
116 mpd_glib_invoke(const struct mpd_glib_source *source)
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);
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)
131         assert(source->id == 0);
132         assert(!source->destroyed);
134         source->callback(error, server_error, message,
135                          0, source->callback_ctx);
138 static void
139 mpd_glib_invoke_async_error(const struct mpd_glib_source *source)
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));
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)
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;
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)
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;
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)
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;
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)
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;
269 static gboolean
270 mpd_glib_source_callback(gcc_unused GIOChannel *_source,
271                          GIOCondition condition, gpointer data)
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;
325 static void
326 mpd_glib_add_watch(struct mpd_glib_source *source)
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;
341 bool
342 mpd_glib_enter(struct mpd_glib_source *source)
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;
365 bool
366 mpd_glib_leave(struct mpd_glib_source *source)
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;