Compiler.h: move to util/
[ncmpc-debian.git] / src / gidle.cxx
1 /* ncmpc (Ncurses MPD Client)
2    (c) 2004-2018 The Music Player Daemon Project
3    Project homepage: http://musicpd.org
4
5    Redistribution and use in source and binary forms, with or without
6    modification, are permitted provided that the following conditions
7    are met:
8
9    - Redistributions of source code must retain the above copyright
10    notice, this list of conditions and the following disclaimer.
11
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.
15
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 */
28
29 #include "gidle.hxx"
30 #include "util/Compiler.h"
31
32 #include <mpd/async.h>
33 #include <mpd/parser.h>
34
35 #include <glib.h>
36
37 #include <assert.h>
38 #include <string.h>
39 #include <errno.h>
40
41 MpdIdleSource::MpdIdleSource(struct mpd_connection &_connection,
42                              mpd_glib_callback_t _callback, void *_callback_ctx)
43         :connection(&_connection),
44          async(mpd_connection_get_async(connection)),
45          parser(mpd_parser_new()),
46          callback(_callback), callback_ctx(_callback_ctx),
47          channel(g_io_channel_unix_new(mpd_async_get_fd(async)))
48 {
49         /* TODO check parser!=nullptr */
50 }
51
52 MpdIdleSource::~MpdIdleSource()
53 {
54         if (id != 0)
55                 g_source_remove(id);
56
57         g_io_channel_unref(channel);
58
59         mpd_parser_free(parser);
60
61 }
62
63 static void
64 mpd_glib_invoke(const MpdIdleSource *source)
65 {
66         assert(source->id == 0);
67
68         if (source->idle_events != 0)
69                 source->callback(MPD_ERROR_SUCCESS, (enum mpd_server_error)0,
70                                  nullptr,
71                                  source->idle_events, source->callback_ctx);
72 }
73
74 static void
75 mpd_glib_invoke_error(const MpdIdleSource *source,
76                       enum mpd_error error, enum mpd_server_error server_error,
77                       const char *message)
78 {
79         assert(source->id == 0);
80
81         source->callback(error, server_error, message,
82                          0, source->callback_ctx);
83 }
84
85 static void
86 mpd_glib_invoke_async_error(const MpdIdleSource *source)
87 {
88         assert(source->id == 0);
89
90         mpd_glib_invoke_error(source, mpd_async_get_error(source->async),
91                               (enum mpd_server_error)0,
92                               mpd_async_get_error_message(source->async));
93 }
94
95 /**
96  * Converts a GIOCondition bit mask to #mpd_async_event.
97  */
98 static enum mpd_async_event
99 g_io_condition_to_mpd_async_event(GIOCondition condition)
100 {
101         unsigned events = 0;
102
103         if (condition & G_IO_IN)
104                 events |= MPD_ASYNC_EVENT_READ;
105
106         if (condition & G_IO_OUT)
107                 events |= MPD_ASYNC_EVENT_WRITE;
108
109         if (condition & G_IO_HUP)
110                 events |= MPD_ASYNC_EVENT_HUP;
111
112         if (condition & G_IO_ERR)
113                 events |= MPD_ASYNC_EVENT_ERROR;
114
115         return (enum mpd_async_event)events;
116 }
117
118 /**
119  * Converts a #mpd_async_event bit mask to GIOCondition.
120  */
121 static GIOCondition
122 mpd_async_events_to_g_io_condition(enum mpd_async_event events)
123 {
124         unsigned condition = 0;
125
126         if (events & MPD_ASYNC_EVENT_READ)
127                 condition |= G_IO_IN;
128
129         if (events & MPD_ASYNC_EVENT_WRITE)
130                 condition |= G_IO_OUT;
131
132         if (events & MPD_ASYNC_EVENT_HUP)
133                 condition |= G_IO_HUP;
134
135         if (events & MPD_ASYNC_EVENT_ERROR)
136                 condition |= G_IO_ERR;
137
138         return GIOCondition(condition);
139 }
140
141 /**
142  * Parses a response line from MPD.
143  *
144  * @return true on success, false on error
145  */
146 static bool
147 mpd_glib_feed(MpdIdleSource *source, char *line)
148 {
149         enum mpd_parser_result result;
150
151         result = mpd_parser_feed(source->parser, line);
152         switch (result) {
153         case MPD_PARSER_MALFORMED:
154                 source->id = 0;
155                 source->io_events = 0;
156
157                 mpd_glib_invoke_error(source, MPD_ERROR_MALFORMED,
158                                       (enum mpd_server_error)0,
159                                       "Malformed MPD response");
160                 return false;
161
162         case MPD_PARSER_SUCCESS:
163                 source->id = 0;
164                 source->io_events = 0;
165
166                 mpd_glib_invoke(source);
167                 return false;
168
169         case MPD_PARSER_ERROR:
170                 source->id = 0;
171                 source->io_events = 0;
172
173                 mpd_glib_invoke_error(source, MPD_ERROR_SERVER,
174                                       mpd_parser_get_server_error(source->parser),
175                                       mpd_parser_get_message(source->parser));
176                 return false;
177
178         case MPD_PARSER_PAIR:
179                 if (strcmp(mpd_parser_get_name(source->parser),
180                            "changed") == 0)
181                         source->idle_events |=
182                                 mpd_idle_name_parse(mpd_parser_get_value(source->parser));
183
184                 break;
185         }
186
187         return true;
188 }
189
190 /**
191  * Receives and evaluates a portion of the MPD response.
192  *
193  * @return true on success, false on error
194  */
195 static bool
196 mpd_glib_recv(MpdIdleSource *source)
197 {
198         char *line;
199         while ((line = mpd_async_recv_line(source->async)) != nullptr) {
200                 if (!mpd_glib_feed(source, line))
201                         return false;
202         }
203
204         if (mpd_async_get_error(source->async) != MPD_ERROR_SUCCESS) {
205                 source->id = 0;
206                 source->io_events = 0;
207
208                 mpd_glib_invoke_async_error(source);
209                 return false;
210         }
211
212         return true;
213 }
214
215 static gboolean
216 mpd_glib_source_callback(gcc_unused GIOChannel *_source,
217                          GIOCondition condition, gpointer data)
218 {
219         auto *source = (MpdIdleSource *)data;
220
221         assert(source->id != 0);
222         assert(source->io_events != 0);
223
224         /* let libmpdclient do some I/O */
225
226         if (!mpd_async_io(source->async,
227                           g_io_condition_to_mpd_async_event(condition))) {
228                 source->id = 0;
229                 source->io_events = 0;
230
231                 mpd_glib_invoke_async_error(source);
232                 return false;
233         }
234
235         /* receive the response */
236
237         if ((condition & G_IO_IN) != 0) {
238                 if (!mpd_glib_recv(source))
239                         return false;
240         }
241
242         /* continue polling? */
243
244         enum mpd_async_event events = mpd_async_events(source->async);
245         if (events == 0) {
246                 /* no events - disable watch */
247                 source->id = 0;
248                 source->io_events = 0;
249
250                 return false;
251         } else if (events != source->io_events) {
252                 /* different event mask: make new watch */
253
254                 g_source_remove(source->id);
255
256                 condition = mpd_async_events_to_g_io_condition(events);
257                 source->id = g_io_add_watch(source->channel, condition,
258                                             mpd_glib_source_callback, source);
259                 source->io_events = events;
260
261                 return false;
262         } else
263                 /* same event mask as before, enable the old watch */
264                 return true;
265 }
266
267 static void
268 mpd_glib_add_watch(MpdIdleSource *source)
269 {
270         enum mpd_async_event events = mpd_async_events(source->async);
271
272         assert(source->io_events == 0);
273         assert(source->id == 0);
274
275         GIOCondition condition = mpd_async_events_to_g_io_condition(events);
276         source->id = g_io_add_watch(source->channel, condition,
277                                     mpd_glib_source_callback, source);
278         source->io_events = events;
279 }
280
281 bool
282 MpdIdleSource::Enter()
283 {
284         assert(io_events == 0);
285         assert(id == 0);
286
287         idle_events = 0;
288
289         if (!mpd_async_send_command(async, "idle", nullptr)) {
290                 mpd_glib_invoke_async_error(this);
291                 return false;
292         }
293
294         mpd_glib_add_watch(this);
295         return true;
296 }
297
298 void
299 MpdIdleSource::Leave()
300 {
301         if (id == 0)
302                 /* already left, callback was invoked */
303                 return;
304
305         g_source_remove(id);
306         id = 0;
307         io_events = 0;
308
309         enum mpd_idle events = idle_events == 0
310                 ? mpd_run_noidle(connection)
311                 : mpd_recv_idle(connection, false);
312
313         if (events == 0 &&
314             mpd_connection_get_error(connection) != MPD_ERROR_SUCCESS) {
315                 enum mpd_error error =
316                         mpd_connection_get_error(connection);
317                 enum mpd_server_error server_error =
318                         error == MPD_ERROR_SERVER
319                         ? mpd_connection_get_server_error(connection)
320                         : (enum mpd_server_error)0;
321
322                 mpd_glib_invoke_error(this, error, server_error,
323                                       mpd_connection_get_error_message(connection));
324                 return;
325         }
326
327         idle_events |= events;
328         mpd_glib_invoke(this);
329 }