Code

9c720866770f415bfe504470c57dbf2cd2d39941
[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;
55 };
57 struct mpd_glib_source *
58 mpd_glib_new(struct mpd_connection *connection,
59              mpd_glib_callback_t callback, void *callback_ctx)
60 {
61         struct mpd_glib_source *source = g_new(struct mpd_glib_source, 1);
63         source->connection = connection;
64         source->async = mpd_connection_get_async(connection);
65         source->parser = mpd_parser_new();
66         /* XXX check source->parser!=NULL */
68         source->callback = callback;
69         source->callback_ctx = callback_ctx;
71         source->channel = g_io_channel_unix_new(mpd_async_get_fd(source->async));
72         source->io_events = 0;
73         source->id = 0;
75         return source;
76 }
78 void
79 mpd_glib_free(struct mpd_glib_source *source)
80 {
81         if (source->id != 0)
82                 g_source_remove(source->id);
84         g_io_channel_unref(source->channel);
85 }
87 static void
88 mpd_glib_invoke(const struct mpd_glib_source *source)
89 {
90         assert(source->id == 0);
92         if (source->idle_events != 0)
93                 source->callback(MPD_ERROR_SUCCESS, 0, NULL,
94                                  source->idle_events, source->callback_ctx);
95 }
97 static void
98 mpd_glib_invoke_error(const struct mpd_glib_source *source,
99                       enum mpd_error error, enum mpd_server_error server_error,
100                       const char *message)
102         assert(source->id == 0);
104         source->callback(error, server_error, message,
105                          0, source->callback_ctx);
108 static void
109 mpd_glib_invoke_async_error(const struct mpd_glib_source *source)
111         assert(source->id == 0);
113         mpd_glib_invoke_error(source, mpd_async_get_error(source->async), 0,
114                               mpd_async_get_error_message(source->async));
117 /**
118  * Converts a GIOCondition bit mask to #mpd_async_event.
119  */
120 static enum mpd_async_event
121 g_io_condition_to_mpd_async_event(GIOCondition condition)
123         enum mpd_async_event events = 0;
125         if (condition & G_IO_IN)
126                 events |= MPD_ASYNC_EVENT_READ;
128         if (condition & G_IO_OUT)
129                 events |= MPD_ASYNC_EVENT_WRITE;
131         if (condition & G_IO_HUP)
132                 events |= MPD_ASYNC_EVENT_HUP;
134         if (condition & G_IO_ERR)
135                 events |= MPD_ASYNC_EVENT_ERROR;
137         return events;
140 /**
141  * Converts a #mpd_async_event bit mask to GIOCondition.
142  */
143 static GIOCondition
144 mpd_async_events_to_g_io_condition(enum mpd_async_event events)
146         GIOCondition condition = 0;
148         if (events & MPD_ASYNC_EVENT_READ)
149                 condition |= G_IO_IN;
151         if (events & MPD_ASYNC_EVENT_WRITE)
152                 condition |= G_IO_OUT;
154         if (events & MPD_ASYNC_EVENT_HUP)
155                 condition |= G_IO_HUP;
157         if (events & MPD_ASYNC_EVENT_ERROR)
158                 condition |= G_IO_ERR;
160         return condition;
163 /**
164  * Parses a response line from MPD.
165  *
166  * @return true on success, false on error
167  */
168 static bool
169 mpd_glib_feed(struct mpd_glib_source *source, char *line)
171         enum mpd_parser_result result;
173         result = mpd_parser_feed(source->parser, line);
174         switch (result) {
175         case MPD_PARSER_MALFORMED:
176                 source->id = 0;
177                 source->io_events = 0;
179                 mpd_glib_invoke_error(source, MPD_ERROR_MALFORMED, 0,
180                                       "Malformed MPD response");
181                 return false;
183         case MPD_PARSER_SUCCESS:
184                 source->id = 0;
185                 source->io_events = 0;
187                 mpd_glib_invoke(source);
188                 return false;
190         case MPD_PARSER_ERROR:
191                 source->id = 0;
192                 source->io_events = 0;
194                 mpd_glib_invoke_error(source, MPD_ERROR_SERVER,
195                                       mpd_parser_get_server_error(source->parser),
196                                       mpd_parser_get_message(source->parser));
197                 return false;
199         case MPD_PARSER_PAIR:
200                 if (strcmp(mpd_parser_get_name(source->parser),
201                            "changed") == 0)
202                         source->idle_events |=
203                                 mpd_idle_name_parse(mpd_parser_get_value(source->parser));
205                 break;
206         }
208         return true;
211 /**
212  * Receives and evaluates a portion of the MPD response.
213  *
214  * @return true on success, false on error
215  */
216 static bool
217 mpd_glib_recv(struct mpd_glib_source *source)
219         char *line;
220         bool success;
222         while ((line = mpd_async_recv_line(source->async)) != NULL) {
223                 success = mpd_glib_feed(source, line);
224                 if (!success)
225                         return false;
226         }
228         if (mpd_async_get_error(source->async) != MPD_ERROR_SUCCESS) {
229                 source->id = 0;
230                 source->io_events = 0;
232                 mpd_glib_invoke_async_error(source);
233                 return false;
234         }
236         return true;
239 static gboolean
240 mpd_glib_source_callback(G_GNUC_UNUSED GIOChannel *_source,
241                          GIOCondition condition, gpointer data)
243         struct mpd_glib_source *source = data;
244         bool success;
245         enum mpd_async_event events;
247         assert(source->id != 0);
248         assert(source->io_events != 0);
250         /* let libmpdclient do some I/O */
252         success = mpd_async_io(source->async,
253                                g_io_condition_to_mpd_async_event(condition));
254         if (!success) {
255                 source->id = 0;
256                 source->io_events = 0;
258                 mpd_glib_invoke_async_error(source);
259                 return false;
260         }
262         /* receive the response */
264         if ((condition & G_IO_IN) != 0) {
265                 success = mpd_glib_recv(source);
266                 if (!success)
267                         return false;
268         }
270         /* continue polling? */
272         events = mpd_async_events(source->async);
273         if (events == 0) {
274                 /* no events - disable watch */
275                 source->id = 0;
276                 source->io_events = 0;
278                 return false;
279         } else if (events != source->io_events) {
280                 /* different event mask: make new watch */
282                 g_source_remove(source->id);
284                 condition = mpd_async_events_to_g_io_condition(events);
285                 source->id = g_io_add_watch(source->channel, condition,
286                                             mpd_glib_source_callback, source);
287                 source->io_events = events;
289                 return false;
290         } else
291                 /* same event mask as before, enable the old watch */
292                 return true;
295 static void
296 mpd_glib_add_watch(struct mpd_glib_source *source)
298         enum mpd_async_event events = mpd_async_events(source->async);
299         GIOCondition condition;
301         assert(source->io_events == 0);
302         assert(source->id == 0);
304         condition = mpd_async_events_to_g_io_condition(events);
306         source->id = g_io_add_watch(source->channel, condition,
307                                     mpd_glib_source_callback, source);
308         source->io_events = events;
311 void
312 mpd_glib_enter(struct mpd_glib_source *source)
314         bool success;
316         assert(source->io_events == 0);
317         assert(source->id == 0);
319         source->idle_events = 0;
321         success = mpd_async_send_command(source->async, "idle", NULL);
322         if (!success) {
323                 mpd_glib_invoke_async_error(source);
324                 return;
325         }
327         mpd_glib_add_watch(source);
330 void
331 mpd_glib_leave(struct mpd_glib_source *source)
333         enum mpd_idle events;
335         if (source->id == 0)
336                 /* already left, callback was invoked */
337                 return;
339         g_source_remove(source->id);
340         source->id = 0;
341         source->io_events = 0;
343         events = source->idle_events == 0
344                 ? mpd_run_noidle(source->connection)
345                 : mpd_recv_idle(source->connection, false);
347         if (events == 0 &&
348             mpd_connection_get_error(source->connection) != MPD_ERROR_SUCCESS) {
349                 enum mpd_error error =
350                         mpd_connection_get_error(source->connection);
351                 enum mpd_server_error server_error =
352                         error == MPD_ERROR_SERVER
353                         ? mpd_connection_get_server_error(source->connection)
354                         : 0;
356                 mpd_glib_invoke_error(source, error, server_error,
357                                       mpd_connection_get_error_message(source->connection));
358                 return;
359         }
361         source->idle_events |= events;
362         mpd_glib_invoke(source);