1 /* libmpdclient
2 (c) 2003-2019 The Music Player Daemon Project
3 This project's homepage is: http://www.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 - Neither the name of the Music Player Daemon nor the names of its
17 contributors may be used to endorse or promote products derived from
18 this software without specific prior written permission.
19
20 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
24 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 #include <mpd/search.h>
34 #include <mpd/send.h>
35 #include <mpd/pair.h>
36 #include <mpd/recv.h>
37 #include "internal.h"
38 #include "iso8601.h"
39
40 #include <assert.h>
41 #include <stdlib.h>
42 #include <stdio.h>
43 #include <string.h>
44
45 static bool
mpd_search_init(struct mpd_connection * connection,const char * cmd)46 mpd_search_init(struct mpd_connection *connection, const char *cmd)
47 {
48 assert(connection != NULL);
49 assert(cmd != NULL);
50
51 if (mpd_error_is_defined(&connection->error))
52 return false;
53
54 if (connection->request) {
55 mpd_error_code(&connection->error, MPD_ERROR_STATE);
56 mpd_error_message(&connection->error,
57 "search already in progress");
58 return false;
59 }
60
61 connection->request = strdup(cmd);
62 if (connection->request == NULL) {
63 mpd_error_code(&connection->error, MPD_ERROR_OOM);
64 return false;
65 }
66
67 return true;
68 }
69
70 bool
mpd_search_db_songs(struct mpd_connection * connection,bool exact)71 mpd_search_db_songs(struct mpd_connection *connection, bool exact)
72 {
73 return mpd_search_init(connection,
74 exact ? "find" : "search");
75 }
76
77 bool
mpd_search_add_db_songs(struct mpd_connection * connection,bool exact)78 mpd_search_add_db_songs(struct mpd_connection *connection, bool exact)
79 {
80 return mpd_search_init(connection,
81 exact ? "findadd" : "searchadd");
82 }
83
84 bool
mpd_search_queue_songs(struct mpd_connection * connection,bool exact)85 mpd_search_queue_songs(struct mpd_connection *connection, bool exact)
86 {
87 return mpd_search_init(connection,
88 exact ? "playlistfind" : "playlistsearch");
89 }
90
91 bool
mpd_search_db_tags(struct mpd_connection * connection,enum mpd_tag_type type)92 mpd_search_db_tags(struct mpd_connection *connection, enum mpd_tag_type type)
93 {
94 assert(connection != NULL);
95
96 if (mpd_error_is_defined(&connection->error))
97 return false;
98
99 if (connection->request) {
100 mpd_error_code(&connection->error, MPD_ERROR_STATE);
101 mpd_error_message(&connection->error,
102 "search already in progress");
103 return false;
104 }
105
106 const char *strtype = mpd_tag_name(type);
107 if (strtype == NULL) {
108 mpd_error_code(&connection->error, MPD_ERROR_ARGUMENT);
109 mpd_error_message(&connection->error,
110 "invalid type specified");
111 return false;
112 }
113
114 const size_t len = 5 + strlen(strtype) + 1;
115 connection->request = malloc(len);
116 if (connection->request == NULL) {
117 mpd_error_code(&connection->error, MPD_ERROR_OOM);
118 return false;
119 }
120
121 snprintf(connection->request, len, "list %s", strtype);
122
123 return true;
124 }
125
126 bool
mpd_count_db_songs(struct mpd_connection * connection)127 mpd_count_db_songs(struct mpd_connection *connection)
128 {
129 assert(connection != NULL);
130
131 return mpd_search_init(connection, "count");
132 }
133
134 static char *
mpd_search_prepare_append(struct mpd_connection * connection,size_t add_length)135 mpd_search_prepare_append(struct mpd_connection *connection,
136 size_t add_length)
137 {
138 assert(connection != NULL);
139
140 if (mpd_error_is_defined(&connection->error))
141 return NULL;
142
143 if (connection->request == NULL) {
144 mpd_error_code(&connection->error, MPD_ERROR_STATE);
145 mpd_error_message(&connection->error,
146 "no search in progress");
147 return NULL;
148 }
149
150 const size_t old_length = strlen(connection->request);
151 char *new_request = realloc(connection->request,
152 old_length + add_length + 1);
153 if (new_request == NULL) {
154 mpd_error_code(&connection->error, MPD_ERROR_OOM);
155 return NULL;
156 }
157
158 connection->request = new_request;
159 return new_request + old_length;
160 }
161
162 static char *
mpd_sanitize_arg(const char * src)163 mpd_sanitize_arg(const char *src)
164 {
165 assert(src != NULL);
166
167 /* instead of counting in that loop above, just
168 * use a bit more memory and half running time
169 */
170 char *result = malloc(strlen(src) * 2 + 1);
171 if (result == NULL)
172 return NULL;
173
174 char *dest = result;
175 char ch;
176 do {
177 ch = *src++;
178 if (ch == '"' || ch == '\\')
179 *dest++ = '\\';
180 *dest++ = ch;
181 } while (ch != 0);
182
183 return result;
184 }
185
186 static bool
mpd_search_add_constraint(struct mpd_connection * connection,mpd_unused enum mpd_operator oper,const char * name,const char * value)187 mpd_search_add_constraint(struct mpd_connection *connection,
188 mpd_unused enum mpd_operator oper,
189 const char *name,
190 const char *value)
191 {
192 assert(connection != NULL);
193 assert(name != NULL);
194 assert(value != NULL);
195
196 char *arg = mpd_sanitize_arg(value);
197 if (arg == NULL) {
198 mpd_error_code(&connection->error, MPD_ERROR_OOM);
199 return false;
200 }
201
202 const size_t add_length = 1 + strlen(name) + 2 + strlen(arg) + 1;
203
204 char *dest = mpd_search_prepare_append(connection, add_length);
205 if (dest == NULL) {
206 free(arg);
207 return false;
208 }
209
210 sprintf(dest, " %s \"%s\"", name, arg);
211
212 free(arg);
213 return true;
214 }
215 bool
mpd_search_add_base_constraint(struct mpd_connection * connection,enum mpd_operator oper,const char * value)216 mpd_search_add_base_constraint(struct mpd_connection *connection,
217 enum mpd_operator oper,
218 const char *value)
219 {
220 return mpd_search_add_constraint(connection, oper, "base", value);
221 }
222
223 bool
mpd_search_add_uri_constraint(struct mpd_connection * connection,enum mpd_operator oper,const char * value)224 mpd_search_add_uri_constraint(struct mpd_connection *connection,
225 enum mpd_operator oper,
226 const char *value)
227 {
228 return mpd_search_add_constraint(connection, oper, "file", value);
229 }
230
231 bool
mpd_search_add_tag_constraint(struct mpd_connection * connection,enum mpd_operator oper,enum mpd_tag_type type,const char * value)232 mpd_search_add_tag_constraint(struct mpd_connection *connection,
233 enum mpd_operator oper,
234 enum mpd_tag_type type, const char *value)
235 {
236 assert(connection != NULL);
237 assert(value != NULL);
238
239 const char *strtype = mpd_tag_name(type);
240 if (strtype == NULL) {
241 mpd_error_code(&connection->error, MPD_ERROR_ARGUMENT);
242 mpd_error_message(&connection->error,
243 "invalid type specified");
244 return false;
245 }
246
247 return mpd_search_add_constraint(connection, oper, strtype, value);
248 }
249
250 bool
mpd_search_add_any_tag_constraint(struct mpd_connection * connection,enum mpd_operator oper,const char * value)251 mpd_search_add_any_tag_constraint(struct mpd_connection *connection,
252 enum mpd_operator oper,
253 const char *value)
254 {
255 return mpd_search_add_constraint(connection, oper, "any", value);
256 }
257
258 bool
mpd_search_add_modified_since_constraint(struct mpd_connection * connection,enum mpd_operator oper,time_t value)259 mpd_search_add_modified_since_constraint(struct mpd_connection *connection,
260 enum mpd_operator oper,
261 time_t value)
262 {
263 char buffer[64];
264 if (!iso8601_datetime_format(buffer, sizeof(buffer), value)) {
265 mpd_error_code(&connection->error, MPD_ERROR_ARGUMENT);
266 mpd_error_message(&connection->error,
267 "failed to format time stamp");
268 return false;
269 }
270
271 return mpd_search_add_constraint(connection, oper,
272 "modified-since", buffer);
273 }
274
275 bool
mpd_search_add_expression(struct mpd_connection * connection,const char * expression)276 mpd_search_add_expression(struct mpd_connection *connection,
277 const char *expression)
278 {
279 assert(connection != NULL);
280 assert(expression != NULL);
281
282 char *arg = mpd_sanitize_arg(expression);
283 if (arg == NULL) {
284 mpd_error_code(&connection->error, MPD_ERROR_OOM);
285 return false;
286 }
287
288 const size_t add_length = 2 + strlen(arg) + 1;
289
290 char *dest = mpd_search_prepare_append(connection, add_length);
291 if (dest == NULL) {
292 free(arg);
293 return false;
294 }
295
296 sprintf(dest, " \"%s\"", arg);
297
298 free(arg);
299 return true;
300 }
301
302 bool
mpd_search_add_group_tag(struct mpd_connection * connection,enum mpd_tag_type type)303 mpd_search_add_group_tag(struct mpd_connection *connection,
304 enum mpd_tag_type type)
305 {
306 assert(connection != NULL);
307
308 const size_t size = 64;
309 char *dest = mpd_search_prepare_append(connection, size);
310 if (dest == NULL)
311 return false;
312
313 snprintf(dest, size, " group %s", mpd_tag_name(type));
314 return true;
315 }
316
317 bool
mpd_search_add_sort_name(struct mpd_connection * connection,const char * name,bool descending)318 mpd_search_add_sort_name(struct mpd_connection *connection,
319 const char *name, bool descending)
320 {
321 assert(connection != NULL);
322
323 const size_t size = 64;
324 char *dest = mpd_search_prepare_append(connection, size);
325 if (dest == NULL)
326 return false;
327
328 snprintf(dest, size, " sort %s%s",
329 descending ? "-" : "",
330 name);
331 return true;
332 }
333
334 bool
mpd_search_add_sort_tag(struct mpd_connection * connection,enum mpd_tag_type type,bool descending)335 mpd_search_add_sort_tag(struct mpd_connection *connection,
336 enum mpd_tag_type type, bool descending)
337 {
338 return mpd_search_add_sort_name(connection,
339 mpd_tag_name(type),
340 descending);
341 }
342
343 bool
mpd_search_add_window(struct mpd_connection * connection,unsigned start,unsigned end)344 mpd_search_add_window(struct mpd_connection *connection,
345 unsigned start, unsigned end)
346 {
347 assert(connection != NULL);
348 assert(start <= end);
349
350 const size_t size = 64;
351 char *dest = mpd_search_prepare_append(connection, size);
352 if (dest == NULL)
353 return false;
354
355 snprintf(dest, size, " window %u:%u", start, end);
356 return true;
357 }
358
359 bool
mpd_search_add_position(struct mpd_connection * connection,unsigned position,enum mpd_position_whence whence)360 mpd_search_add_position(struct mpd_connection *connection,
361 unsigned position, enum mpd_position_whence whence)
362 {
363 assert(connection != NULL);
364
365 const size_t size = 64;
366 char *dest = mpd_search_prepare_append(connection, size);
367 if (dest == NULL)
368 return false;
369
370 const char *whence_s = mpd_position_whence_char(whence);
371
372 snprintf(dest, size, " position %s%u", whence_s, position);
373 return true;
374 }
375
376 bool
mpd_search_commit(struct mpd_connection * connection)377 mpd_search_commit(struct mpd_connection *connection)
378 {
379 assert(connection != NULL);
380
381 if (mpd_error_is_defined(&connection->error)) {
382 mpd_search_cancel(connection);
383 return false;
384 }
385
386 if (connection->request == NULL) {
387 mpd_error_code(&connection->error, MPD_ERROR_STATE);
388 mpd_error_message(&connection->error,
389 "no search in progress");
390 return false;
391 }
392
393 bool success = mpd_send_command(connection, connection->request, NULL);
394 free(connection->request);
395 connection->request = NULL;
396
397 return success;
398 }
399
400 void
mpd_search_cancel(struct mpd_connection * connection)401 mpd_search_cancel(struct mpd_connection *connection)
402 {
403 assert(connection != NULL);
404
405 free(connection->request);
406 connection->request = NULL;
407 }
408
409 struct mpd_pair *
mpd_recv_pair_tag(struct mpd_connection * connection,enum mpd_tag_type type)410 mpd_recv_pair_tag(struct mpd_connection *connection, enum mpd_tag_type type)
411 {
412 assert(connection != NULL);
413
414 const char *name = mpd_tag_name(type);
415 if (name == NULL)
416 return NULL;
417
418 return mpd_recv_pair_named(connection, name);
419 }
420
421 bool
mpd_search_add_db_songs_to_playlist(struct mpd_connection * connection,const char * playlist_name)422 mpd_search_add_db_songs_to_playlist(struct mpd_connection *connection,
423 const char *playlist_name)
424 {
425 assert(connection != NULL);
426 assert(playlist_name != NULL);
427
428 if (mpd_error_is_defined(&connection->error))
429 return false;
430
431 if (connection->request) {
432 mpd_error_code(&connection->error, MPD_ERROR_STATE);
433 mpd_error_message(&connection->error,
434 "search already in progress");
435 return false;
436 }
437
438 char *arg = mpd_sanitize_arg(playlist_name);
439 if (arg == NULL) {
440 mpd_error_code(&connection->error, MPD_ERROR_OOM);
441 return false;
442 }
443
444 const size_t len = 13 + strlen(arg) + 2;
445 connection->request = malloc(len);
446 if (connection->request == NULL) {
447 free(arg);
448 mpd_error_code(&connection->error, MPD_ERROR_OOM);
449 return false;
450 }
451
452 snprintf(connection->request, len, "searchaddpl \"%s\" ", arg);
453
454 free(arg);
455 return true;
456 }
457