Code

release v0.29
[ncmpc.git] / src / gidle.c
1 /* ncmpc (Ncurses MPD Client)
2    (c) 2004-2017 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         while ((line = mpd_async_recv_line(source->async)) != NULL) {
251                 if (!mpd_glib_feed(source, line))
252                         return false;
253         }
255         if (mpd_async_get_error(source->async) != MPD_ERROR_SUCCESS) {
256                 source->id = 0;
257                 source->io_events = 0;
259                 mpd_glib_invoke_async_error(source);
260                 return false;
261         }
263         return true;
266 static gboolean
267 mpd_glib_source_callback(gcc_unused GIOChannel *_source,
268                          GIOCondition condition, gpointer data)
270         struct mpd_glib_source *source = data;
272         assert(source->id != 0);
273         assert(source->io_events != 0);
275         /* let libmpdclient do some I/O */
277         if (!mpd_async_io(source->async,
278                           g_io_condition_to_mpd_async_event(condition))) {
279                 source->id = 0;
280                 source->io_events = 0;
282                 mpd_glib_invoke_async_error(source);
283                 return false;
284         }
286         /* receive the response */
288         if ((condition & G_IO_IN) != 0) {
289                 if (!mpd_glib_recv(source))
290                         return false;
291         }
293         /* continue polling? */
295         enum mpd_async_event events = mpd_async_events(source->async);
296         if (events == 0) {
297                 /* no events - disable watch */
298                 source->id = 0;
299                 source->io_events = 0;
301                 return false;
302         } else if (events != source->io_events) {
303                 /* different event mask: make new watch */
305                 g_source_remove(source->id);
307                 condition = mpd_async_events_to_g_io_condition(events);
308                 source->id = g_io_add_watch(source->channel, condition,
309                                             mpd_glib_source_callback, source);
310                 source->io_events = events;
312                 return false;
313         } else
314                 /* same event mask as before, enable the old watch */
315                 return true;
318 static void
319 mpd_glib_add_watch(struct mpd_glib_source *source)
321         enum mpd_async_event events = mpd_async_events(source->async);
323         assert(source->io_events == 0);
324         assert(source->id == 0);
326         GIOCondition condition = mpd_async_events_to_g_io_condition(events);
327         source->id = g_io_add_watch(source->channel, condition,
328                                     mpd_glib_source_callback, source);
329         source->io_events = events;
332 bool
333 mpd_glib_enter(struct mpd_glib_source *source)
335         assert(source->io_events == 0);
336         assert(source->id == 0);
337         assert(!source->destroyed);
339         if (source->leaving)
340                 return false;
342         source->idle_events = 0;
344         if (!mpd_async_send_command(source->async, "idle", NULL)) {
345                 mpd_glib_invoke_async_error(source);
346                 return false;
347         }
349         mpd_glib_add_watch(source);
350         return true;
353 bool
354 mpd_glib_leave(struct mpd_glib_source *source)
356         assert(!source->destroyed);
358         if (source->id == 0)
359                 /* already left, callback was invoked */
360                 return true;
362         g_source_remove(source->id);
363         source->id = 0;
364         source->io_events = 0;
366         enum mpd_idle events = source->idle_events == 0
367                 ? mpd_run_noidle(source->connection)
368                 : mpd_recv_idle(source->connection, false);
370         source->leaving = true;
372         if (events == 0 &&
373             mpd_connection_get_error(source->connection) != MPD_ERROR_SUCCESS) {
374                 enum mpd_error error =
375                         mpd_connection_get_error(source->connection);
376                 enum mpd_server_error server_error =
377                         error == MPD_ERROR_SERVER
378                         ? mpd_connection_get_server_error(source->connection)
379                         : 0;
381                 mpd_glib_invoke_error(source, error, server_error,
382                                       mpd_connection_get_error_message(source->connection));
384                 if (source->destroyed) {
385                         g_free(source);
386                         return false;
387                 }
389                 source->leaving = false;
390                 return true;
391         }
393         source->idle_events |= events;
394         mpd_glib_invoke(source);
396         if (source->destroyed) {
397                 g_free(source);
398                 return false;
399         }
401         source->leaving = false;
402         return true;