1 /* ncmpc (Ncurses MPD Client)
2    (c) 2004-2020 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 
31 #include <mpd/async.h>
32 #include <mpd/parser.h>
33 
34 #include <assert.h>
35 #include <string.h>
36 
MpdIdleSource(EventLoop & event_loop,struct mpd_connection & _connection,MpdIdleHandler & _handler)37 MpdIdleSource::MpdIdleSource(EventLoop &event_loop,
38 			     struct mpd_connection &_connection,
39 			     MpdIdleHandler &_handler) noexcept
40 	:connection(&_connection),
41 	 async(mpd_connection_get_async(connection)),
42 	 parser(mpd_parser_new()),
43 	 event(event_loop, BIND_THIS_METHOD(OnSocketReady),
44 	       SocketDescriptor(mpd_async_get_fd(async))),
45 	 handler(_handler)
46 {
47 	/* TODO check parser!=nullptr */
48 }
49 
~MpdIdleSource()50 MpdIdleSource::~MpdIdleSource() noexcept
51 {
52 	mpd_parser_free(parser);
53 
54 }
55 
56 void
InvokeAsyncError()57 MpdIdleSource::InvokeAsyncError() noexcept
58 {
59 	InvokeError(mpd_async_get_error(async),
60 		    (enum mpd_server_error)0,
61 		    mpd_async_get_error_message(async));
62 }
63 
64 bool
Feed(char * line)65 MpdIdleSource::Feed(char *line) noexcept
66 {
67 	enum mpd_parser_result result;
68 
69 	result = mpd_parser_feed(parser, line);
70 	switch (result) {
71 	case MPD_PARSER_MALFORMED:
72 		event.Cancel();
73 		io_events = 0;
74 
75 		InvokeError(MPD_ERROR_MALFORMED,
76 			    (enum mpd_server_error)0,
77 			    "Malformed MPD response");
78 		return false;
79 
80 	case MPD_PARSER_SUCCESS:
81 		event.Cancel();
82 		io_events = 0;
83 
84 		InvokeCallback();
85 		return false;
86 
87 	case MPD_PARSER_ERROR:
88 		event.Cancel();
89 		io_events = 0;
90 
91 		InvokeError(MPD_ERROR_SERVER,
92 			    mpd_parser_get_server_error(parser),
93 			    mpd_parser_get_message(parser));
94 		return false;
95 
96 	case MPD_PARSER_PAIR:
97 		if (strcmp(mpd_parser_get_name(parser),
98 			   "changed") == 0)
99 			idle_events |=
100 				mpd_idle_name_parse(mpd_parser_get_value(parser));
101 
102 		break;
103 	}
104 
105 	return true;
106 }
107 
108 bool
Receive()109 MpdIdleSource::Receive() noexcept
110 {
111 	char *line;
112 	while ((line = mpd_async_recv_line(async)) != nullptr) {
113 		if (!Feed(line))
114 			return false;
115 	}
116 
117 	if (mpd_async_get_error(async) != MPD_ERROR_SUCCESS) {
118 		event.Cancel();
119 		io_events = 0;
120 
121 		InvokeAsyncError();
122 		return false;
123 	}
124 
125 	return true;
126 }
127 
128 void
OnSocketReady(unsigned flags)129 MpdIdleSource::OnSocketReady(unsigned flags) noexcept
130 {
131 	unsigned events = 0;
132 	if (flags & SocketEvent::READ)
133 		events |= MPD_ASYNC_EVENT_READ;
134 	if (flags & SocketEvent::WRITE)
135 		events |= MPD_ASYNC_EVENT_WRITE;
136 
137 	if (!mpd_async_io(async, (enum mpd_async_event)events)) {
138 		event.Cancel();
139 		io_events = 0;
140 
141 		InvokeAsyncError();
142 		return;
143 	}
144 
145 	if (flags & SocketEvent::READ)
146 		if (!Receive())
147 			return;
148 
149 	UpdateSocket();
150 }
151 
152 void
UpdateSocket()153 MpdIdleSource::UpdateSocket() noexcept
154 {
155 	enum mpd_async_event events = mpd_async_events(async);
156 	if (events == io_events)
157 		return;
158 
159 	unsigned flags = 0;
160 	if (events & MPD_ASYNC_EVENT_READ)
161 		flags |= SocketEvent::READ;
162 
163 	if (events & MPD_ASYNC_EVENT_WRITE)
164 		flags |= SocketEvent::WRITE;
165 
166 	event.Schedule(flags);
167 
168 	io_events = events;
169 }
170 
171 bool
Enter()172 MpdIdleSource::Enter() noexcept
173 {
174 	assert(io_events == 0);
175 
176 	idle_events = 0;
177 
178 	if (!mpd_async_send_command(async, "idle", nullptr)) {
179 		InvokeAsyncError();
180 		return false;
181 	}
182 
183 	UpdateSocket();
184 	return true;
185 }
186 
187 void
Leave()188 MpdIdleSource::Leave() noexcept
189 {
190 	if (io_events == 0)
191 		/* already left, callback was invoked */
192 		return;
193 
194 	event.Cancel();
195 	io_events = 0;
196 
197 	enum mpd_idle events = idle_events == 0
198 		? mpd_run_noidle(connection)
199 		: mpd_recv_idle(connection, false);
200 
201 	if (events == 0 &&
202 	    mpd_connection_get_error(connection) != MPD_ERROR_SUCCESS) {
203 		enum mpd_error error =
204 			mpd_connection_get_error(connection);
205 		enum mpd_server_error server_error =
206 			error == MPD_ERROR_SERVER
207 			? mpd_connection_get_server_error(connection)
208 			: (enum mpd_server_error)0;
209 
210 		InvokeError(error, server_error,
211 			    mpd_connection_get_error_message(connection));
212 		return;
213 	}
214 
215 	idle_events |= events;
216 	InvokeCallback();
217 }
218