16901679e84bd9d822f5e558ed34cfd249bc5597
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)
102 {
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);
108 }
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)
114 {
115 assert(source->id == 0);
117 source->callback(error, server_error, message,
118 0, source->callback_ctx);
119 }
121 static void
122 mpd_glib_invoke_async_error(const struct mpd_glib_source *source)
123 {
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));
128 }
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)
135 {
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;
151 }
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)
158 {
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;
174 }
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)
183 {
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;
222 }
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)
231 {
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;
250 }
252 static gboolean
253 mpd_glib_source_callback(G_GNUC_UNUSED GIOChannel *_source,
254 GIOCondition condition, gpointer data)
255 {
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;
306 }
308 static void
309 mpd_glib_add_watch(struct mpd_glib_source *source)
310 {
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;
322 }
324 void
325 mpd_glib_enter(struct mpd_glib_source *source)
326 {
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);
344 }
346 void
347 mpd_glib_leave(struct mpd_glib_source *source)
348 {
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;
384 }