Code

16901679e84bd9d822f5e558ed34cfd249bc5597
[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);
96         mpd_parser_free(source->parser);
97         g_free(source);
98 }
100 static void
101 mpd_glib_invoke(const struct mpd_glib_source *source)
103         assert(source->id == 0);
105         if (source->idle_events != 0)
106                 source->callback(MPD_ERROR_SUCCESS, 0, NULL,
107                                  source->idle_events, source->callback_ctx);
110 static void
111 mpd_glib_invoke_error(const struct mpd_glib_source *source,
112                       enum mpd_error error, enum mpd_server_error server_error,
113                       const char *message)
115         assert(source->id == 0);
117         source->callback(error, server_error, message,
118                          0, source->callback_ctx);
121 static void
122 mpd_glib_invoke_async_error(const struct mpd_glib_source *source)
124         assert(source->id == 0);
126         mpd_glib_invoke_error(source, mpd_async_get_error(source->async), 0,
127                               mpd_async_get_error_message(source->async));
130 /**
131  * Converts a GIOCondition bit mask to #mpd_async_event.
132  */
133 static enum mpd_async_event
134 g_io_condition_to_mpd_async_event(GIOCondition condition)
136         enum mpd_async_event events = 0;
138         if (condition & G_IO_IN)
139                 events |= MPD_ASYNC_EVENT_READ;
141         if (condition & G_IO_OUT)
142                 events |= MPD_ASYNC_EVENT_WRITE;
144         if (condition & G_IO_HUP)
145                 events |= MPD_ASYNC_EVENT_HUP;
147         if (condition & G_IO_ERR)
148                 events |= MPD_ASYNC_EVENT_ERROR;
150         return events;
153 /**
154  * Converts a #mpd_async_event bit mask to GIOCondition.
155  */
156 static GIOCondition
157 mpd_async_events_to_g_io_condition(enum mpd_async_event events)
159         GIOCondition condition = 0;
161         if (events & MPD_ASYNC_EVENT_READ)
162                 condition |= G_IO_IN;
164         if (events & MPD_ASYNC_EVENT_WRITE)
165                 condition |= G_IO_OUT;
167         if (events & MPD_ASYNC_EVENT_HUP)
168                 condition |= G_IO_HUP;
170         if (events & MPD_ASYNC_EVENT_ERROR)
171                 condition |= G_IO_ERR;
173         return condition;
176 /**
177  * Parses a response line from MPD.
178  *
179  * @return true on success, false on error
180  */
181 static bool
182 mpd_glib_feed(struct mpd_glib_source *source, char *line)
184         enum mpd_parser_result result;
186         result = mpd_parser_feed(source->parser, line);
187         switch (result) {
188         case MPD_PARSER_MALFORMED:
189                 source->id = 0;
190                 source->io_events = 0;
192                 mpd_glib_invoke_error(source, MPD_ERROR_MALFORMED, 0,
193                                       "Malformed MPD response");
194                 return false;
196         case MPD_PARSER_SUCCESS:
197                 source->id = 0;
198                 source->io_events = 0;
200                 mpd_glib_invoke(source);
201                 return false;
203         case MPD_PARSER_ERROR:
204                 source->id = 0;
205                 source->io_events = 0;
207                 mpd_glib_invoke_error(source, MPD_ERROR_SERVER,
208                                       mpd_parser_get_server_error(source->parser),
209                                       mpd_parser_get_message(source->parser));
210                 return false;
212         case MPD_PARSER_PAIR:
213                 if (strcmp(mpd_parser_get_name(source->parser),
214                            "changed") == 0)
215                         source->idle_events |=
216                                 mpd_idle_name_parse(mpd_parser_get_value(source->parser));
218                 break;
219         }
221         return true;
224 /**
225  * Receives and evaluates a portion of the MPD response.
226  *
227  * @return true on success, false on error
228  */
229 static bool
230 mpd_glib_recv(struct mpd_glib_source *source)
232         char *line;
233         bool success;
235         while ((line = mpd_async_recv_line(source->async)) != NULL) {
236                 success = mpd_glib_feed(source, line);
237                 if (!success)
238                         return false;
239         }
241         if (mpd_async_get_error(source->async) != MPD_ERROR_SUCCESS) {
242                 source->id = 0;
243                 source->io_events = 0;
245                 mpd_glib_invoke_async_error(source);
246                 return false;
247         }
249         return true;
252 static gboolean
253 mpd_glib_source_callback(G_GNUC_UNUSED GIOChannel *_source,
254                          GIOCondition condition, gpointer data)
256         struct mpd_glib_source *source = data;
257         bool success;
258         enum mpd_async_event events;
260         assert(source->id != 0);
261         assert(source->io_events != 0);
263         /* let libmpdclient do some I/O */
265         success = mpd_async_io(source->async,
266                                g_io_condition_to_mpd_async_event(condition));
267         if (!success) {
268                 source->id = 0;
269                 source->io_events = 0;
271                 mpd_glib_invoke_async_error(source);
272                 return false;
273         }
275         /* receive the response */
277         if ((condition & G_IO_IN) != 0) {
278                 success = mpd_glib_recv(source);
279                 if (!success)
280                         return false;
281         }
283         /* continue polling? */
285         events = mpd_async_events(source->async);
286         if (events == 0) {
287                 /* no events - disable watch */
288                 source->id = 0;
289                 source->io_events = 0;
291                 return false;
292         } else if (events != source->io_events) {
293                 /* different event mask: make new watch */
295                 g_source_remove(source->id);
297                 condition = mpd_async_events_to_g_io_condition(events);
298                 source->id = g_io_add_watch(source->channel, condition,
299                                             mpd_glib_source_callback, source);
300                 source->io_events = events;
302                 return false;
303         } else
304                 /* same event mask as before, enable the old watch */
305                 return true;
308 static void
309 mpd_glib_add_watch(struct mpd_glib_source *source)
311         enum mpd_async_event events = mpd_async_events(source->async);
312         GIOCondition condition;
314         assert(source->io_events == 0);
315         assert(source->id == 0);
317         condition = mpd_async_events_to_g_io_condition(events);
319         source->id = g_io_add_watch(source->channel, condition,
320                                     mpd_glib_source_callback, source);
321         source->io_events = events;
324 void
325 mpd_glib_enter(struct mpd_glib_source *source)
327         bool success;
329         assert(source->io_events == 0);
330         assert(source->id == 0);
332         if (source->leaving)
333                 return;
335         source->idle_events = 0;
337         success = mpd_async_send_command(source->async, "idle", NULL);
338         if (!success) {
339                 mpd_glib_invoke_async_error(source);
340                 return;
341         }
343         mpd_glib_add_watch(source);
346 void
347 mpd_glib_leave(struct mpd_glib_source *source)
349         enum mpd_idle events;
351         if (source->id == 0)
352                 /* already left, callback was invoked */
353                 return;
355         g_source_remove(source->id);
356         source->id = 0;
357         source->io_events = 0;
359         events = source->idle_events == 0
360                 ? mpd_run_noidle(source->connection)
361                 : mpd_recv_idle(source->connection, false);
363         source->leaving = true;
365         if (events == 0 &&
366             mpd_connection_get_error(source->connection) != MPD_ERROR_SUCCESS) {
367                 enum mpd_error error =
368                         mpd_connection_get_error(source->connection);
369                 enum mpd_server_error server_error =
370                         error == MPD_ERROR_SERVER
371                         ? mpd_connection_get_server_error(source->connection)
372                         : 0;
374                 mpd_glib_invoke_error(source, error, server_error,
375                                       mpd_connection_get_error_message(source->connection));
376                 source->leaving = false;
377                 return;
378         }
380         source->idle_events |= events;
381         mpd_glib_invoke(source);
383         source->leaving = false;