1 /* libmpd (high level libmpdclient library)
2 * Copyright (C) 2004-2009 Qball Cow <qball@sarine.nl>
3 * Project homepage: http://gmpcwiki.sarine.nl/
4
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include <stdio.h>
21 #include <stdlib.h>
22 #define __USE_GNU
23
24 #include <string.h>
25 #include <stdarg.h>
26 #include <config.h>
27 #include <glib.h>
28 #include "debug_printf.h"
29
30 #include "libmpd.h"
31 #include "libmpd-internal.h"
32
33 static void mpd_free_queue_ob(MpdObj *mi);
34 static void mpd_server_free_commands(MpdObj *mi);
35
36
37 char *libmpd_version = LIBMPD_VERSION;
38 #ifndef HAVE_STRNDUP
39 /**
40 * Not every platfarm has strndup, so here we have a nice little custom implementation
41 */
strndup(const char * s,size_t n)42 char * strndup(const char *s, size_t n)
43 {
44 size_t nAvail;
45 char *p;
46
47 if(!s) {
48 return NULL;
49 }
50
51 /* nAvail = min( strlen(s)+1, n+1 ); */
52 nAvail=((strlen(s)+1) > (n+1)) ? n+1 : strlen(s)+1;
53 if(!(p=malloc(nAvail))) {
54 return NULL;
55 }
56 memcpy(p, s, nAvail);
57 p[nAvail - 1] = 0;
58 return p;
59 }
60 #endif
61 /**
62 * @param state a #MpdServerState to initialize
63 *
64 * Initialize #MpdServerState. To stop duplicating code.
65 */
mpd_init_MpdServerState(MpdServerState * state)66 static void mpd_init_MpdServerState(MpdServerState *state)
67 {
68 state->playlistid = -1;
69 state->storedplaylistid = -1;
70 state->state = -1;
71 state->songid = -1;
72 state->songpos = -1;
73 state->nextsongpos = -1;
74 state->nextsongid = -1;
75 state->dbUpdateTime = 0;
76 state->updatingDb = 0;
77 state->repeat = -1;
78 state->random = -1;
79 state->volume = -2;
80 state->xfade = -1;
81 state->totaltime = 0;
82 state->elapsedtime = 0;
83 state->bitrate = 0;
84 state->samplerate = 0;
85 state->channels = 0;
86 state->bits = 0;
87 state->consume = 0;
88 state->single = 0;
89 state->playlistLength = 0;
90 state->error[0] = '\0';
91
92 }
93
94
mpd_create()95 static MpdObj * mpd_create()
96 {
97 MpdObj * mi = g_slice_new0(MpdObj);
98 g_return_val_if_fail(mi != NULL, NULL); /* should never happen on linux */
99
100 /* set default values */
101 /* we start not connected */
102 mi->connected = FALSE;
103 /* port 6600 is the default mpd port */
104 mi->port = 6600;
105 /* localhost */
106 mi->hostname = strdup("localhost");
107 /* 1 second timeout */
108 mi->connection_timeout = 1.0;
109 /* info */
110 mpd_init_MpdServerState(&(mi->CurrentState));
111 mpd_init_MpdServerState(&(mi->OldState));
112
113 /* connection is locked because where not connected */
114 mi->connection_lock = TRUE;
115
116 /* search stuff */
117 mi->search_type = MPD_SEARCH_TYPE_NONE;
118 /* no need to initialize, but set it to anything anyway*/
119 mi->search_field = MPD_TAG_ITEM_ARTIST;
120
121 return mi;
122 }
123
mpd_free(MpdObj * mi)124 void mpd_free(MpdObj *mi)
125 {
126 debug_printf(DEBUG_INFO, "destroying MpdObj object\n");
127 if(mi->connected)
128 {
129 /* disconnect */
130 debug_printf(DEBUG_WARNING, "Connection still running, disconnecting\n");
131 mpd_disconnect(mi);
132 }
133 if(mi->hostname)
134 {
135 free(mi->hostname);
136 }
137 if(mi->password)
138 {
139 free(mi->password);
140 }
141 if(mi->error_msg)
142 {
143 free(mi->error_msg);
144 }
145 if(mi->connection)
146 {
147 /* obsolete */
148 mpd_closeConnection(mi->connection);
149 }
150 if(mi->status)
151 {
152 mpd_freeStatus(mi->status);
153 }
154 if(mi->stats)
155 {
156 mpd_freeStats(mi->stats);
157 }
158 if(mi->CurrentSong)
159 {
160 mpd_freeSong(mi->CurrentSong);
161 }
162 if(mi->url_handlers)
163 {
164 g_strfreev(mi->url_handlers);
165 mi->url_handlers = NULL;
166 }
167 mpd_free_queue_ob(mi);
168 mpd_server_free_commands(mi);
169 g_slice_free(MpdObj, mi);
170 }
171
mpd_check_error(MpdObj * mi)172 int mpd_check_error(MpdObj *mi)
173 {
174 if(mi == NULL)
175 {
176 debug_printf(DEBUG_ERROR, "mi == NULL?");
177 return MPD_ARGS_ERROR;
178 }
179
180 /* this shouldn't happen, ever */
181 if(mi->connection == NULL)
182 {
183 debug_printf(DEBUG_ERROR, "mi->connection == NULL?");
184 return MPD_FATAL_ERROR;
185 }
186
187 /* TODO: map these errors in the future */
188 mi->error = mi->connection->error;
189 mi->error_mpd_code = mi->connection->errorCode;
190 /*TODO: do I need to strdup this? */
191 if(!g_utf8_validate(mi->connection->errorStr, -1, NULL)){
192 mi->error_msg = g_locale_to_utf8(mi->connection->errorStr, -1, NULL, NULL,NULL);
193 }else{
194 mi->error_msg = g_strdup(mi->connection->errorStr);
195 }
196
197 if(mi->error_msg == NULL) mi->error_msg = g_strdup("Failed to convert error message to utf-8");
198
199 /* Check for permission */
200 /* First check for an error reported by MPD
201 * Then check what type of error mpd reported
202 */
203 if(mi->error == MPD_ERROR_ACK)
204 {
205
206 debug_printf(DEBUG_ERROR,"clearing errors in mpd_Connection: %i-%s", mi->connection->errorCode, mi->connection->errorStr);
207 mpd_clearError(mi->connection);
208 if (mi->the_error_callback)
209 {
210 debug_printf(DEBUG_ERROR, "Error callback 1 (ACK)");
211 if(mi->the_error_callback(mi, mi->error_mpd_code, mi->error_msg, mi->the_error_signal_userdata ))
212 {
213 debug_printf(DEBUG_ERROR, "Error callback told me to disconnect");
214 mpd_disconnect(mi);
215 free(mi->error_msg);
216 mi->error_msg = NULL;
217
218 return MPD_SERVER_ERROR;
219 }
220 }
221 free(mi->error_msg);
222 mi->error_msg = NULL;
223 return TRUE;
224 }
225 if(mi->error)
226 {
227
228 debug_printf(DEBUG_ERROR, "Following error occurred: %i: code: %i msg: %s", mi->error,mi->connection->errorCode, mi->error_msg);
229
230 if (mi->the_error_callback)
231 {
232 debug_printf(DEBUG_ERROR, "Error callback 2");
233 mi->the_error_callback(mi, mi->error, mi->error_msg, mi->the_error_signal_userdata );
234 }
235 mpd_disconnect(mi);
236 free(mi->error_msg);
237 mi->error_msg = NULL;
238
239 return MPD_SERVER_ERROR;
240 }
241 free(mi->error_msg);
242 mi->error_msg = NULL;
243 return MPD_OK;
244 }
245
246
247
mpd_lock_conn(MpdObj * mi)248 int mpd_lock_conn(MpdObj *mi)
249 {
250
251 if(mi->connection_lock)
252 {
253 debug_printf(DEBUG_WARNING, "Failed to lock connection, already locked\n");
254 return MPD_LOCK_FAILED;
255 }
256 mi->connection_lock = TRUE;
257 return MPD_OK;
258 }
259
mpd_unlock_conn(MpdObj * mi)260 int mpd_unlock_conn(MpdObj *mi)
261 {
262 if(!mi->connection_lock)
263 {
264 debug_printf(DEBUG_ERROR, "Failed to unlock connection, already unlocked\n");
265 return MPD_LOCK_FAILED;
266 }
267
268 mi->connection_lock = FALSE;
269
270 return mpd_check_error(mi);
271 }
272
mpd_new_default()273 MpdObj * mpd_new_default()
274 {
275 debug_printf(DEBUG_INFO, "creating a new mpdInt object\n");
276 return mpd_create();
277 }
278
mpd_new(char * hostname,int port,char * password)279 MpdObj *mpd_new(char *hostname, int port, char *password)
280 {
281 MpdObj *mi = mpd_create();
282 if(mi == NULL)
283 {
284 return NULL;
285 }
286 if(hostname != NULL)
287 {
288 mpd_set_hostname(mi, hostname);
289 }
290 if(port != 0)
291 {
292 mpd_set_port(mi, port);
293 }
294 if(password != NULL)
295 {
296 mpd_set_password(mi, password);
297 }
298 return mi;
299 }
300
301
mpd_get_hostname(MpdObj * mi)302 const char * mpd_get_hostname(MpdObj *mi)
303 {
304 if(mi == NULL)
305 {
306 return NULL;
307 }
308 return mi->hostname;
309 }
310
mpd_set_hostname(MpdObj * mi,char * hostname)311 int mpd_set_hostname(MpdObj *mi, char *hostname)
312 {
313 if(mi == NULL)
314 {
315 debug_printf(DEBUG_ERROR, "mi == NULL\n");
316 return MPD_ARGS_ERROR;
317 }
318
319 if(mi->hostname != NULL)
320 {
321 free(mi->hostname);
322 }
323 /* possible location todo some post processing of hostname */
324 mi->hostname = strdup(hostname);
325 return MPD_OK;
326 }
327
mpd_set_password(MpdObj * mi,const char * password)328 int mpd_set_password(MpdObj *mi, const char *password)
329 {
330 if(mi == NULL)
331 {
332 debug_printf(DEBUG_ERROR, "mi == NULL\n");
333 return MPD_ARGS_ERROR;
334 }
335
336 if(mi->password != NULL)
337 {
338 free(mi->password);
339 }
340 /* possible location todo some post processing of password */
341 mi->password = strdup(password);
342 return MPD_OK;
343 }
344
345
mpd_send_password(MpdObj * mi)346 int mpd_send_password(MpdObj *mi)
347 {
348 if(!mi) return MPD_ARGS_ERROR;
349 if(mi->password && mpd_check_connected(mi) && strlen(mi->password))
350 {
351 if(mpd_lock_conn(mi))
352 {
353 debug_printf(DEBUG_WARNING, "failed to lock connection");
354 return MPD_LOCK_FAILED;
355 }
356 mpd_sendPasswordCommand(mi->connection, mi->password);
357 mpd_finishCommand(mi->connection);
358 if(mpd_unlock_conn(mi))
359 {
360 debug_printf(DEBUG_ERROR, "Failed to unlock connection\n");
361 return MPD_LOCK_FAILED;
362 }
363 mpd_server_get_allowed_commands(mi);
364 /*TODO: should I do it here, or in the
365 * mpd_server_get_allowed_command, so it also get's executed on
366 * connect
367 */
368 if((mi->the_status_changed_callback != NULL))
369 {
370 /** update the supported tags */
371 {
372 int i;
373 char **retv = mpd_server_get_tag_types(mi);
374 if(retv){
375 for(i=0;i<MPD_TAG_ITEM_ANY;i++)
376 {
377 int j=0;
378 for(j=0;retv[j] && strcasecmp(retv[j],mpdTagItemKeys[i]); j++);
379 if(retv[j]) mi->supported_tags[i] = TRUE;
380 else mi->supported_tags[i] = FALSE;
381 }
382 g_strfreev(retv);
383 }
384 /* also always true */
385 mi->supported_tags[MPD_TAG_ITEM_FILENAME] = TRUE;
386 mi->supported_tags[MPD_TAG_ITEM_ANY] = TRUE;
387 }
388 /* If permission updates, we should also call an output update, The data might be available now. */
389 mi->the_status_changed_callback( mi,
390 MPD_CST_PERMISSION|MPD_CST_OUTPUT, mi->the_status_changed_signal_userdata );
391 }
392 }
393 return MPD_OK;
394 }
395
mpd_set_port(MpdObj * mi,int port)396 int mpd_set_port(MpdObj *mi, int port)
397 {
398 if(mi == NULL)
399 {
400 debug_printf(DEBUG_ERROR, "mi == NULL\n");
401 return MPD_ARGS_ERROR;
402 }
403 mi->port = port;
404 return MPD_OK;
405 }
406
mpd_set_connection_timeout(MpdObj * mi,float timeout)407 int mpd_set_connection_timeout(MpdObj *mi, float timeout)
408 {
409 if(mi == NULL)
410 {
411 debug_printf(DEBUG_ERROR, "mi == NULL\n");
412 return MPD_ARGS_ERROR;
413 }
414 mi->connection_timeout = timeout;
415 if(mpd_check_connected(mi))
416 {
417 /*TODO: set timeout */
418 if(mpd_lock_conn(mi))
419 {
420 debug_printf(DEBUG_ERROR,"lock failed\n");
421 return MPD_LOCK_FAILED;
422 }
423 mpd_setConnectionTimeout(mi->connection, timeout);
424 mpd_finishCommand(mi->connection);
425
426 mpd_unlock_conn(mi);
427
428 }
429 return MPD_OK;
430 }
431
mpd_server_free_commands(MpdObj * mi)432 static void mpd_server_free_commands(MpdObj *mi)
433 {
434 if(mi->commands)
435 {
436 int i=0;
437 while(mi->commands[i].command_name)
438 {
439 free(mi->commands[i].command_name);
440 i++;
441 }
442 free(mi->commands);
443 mi->commands = NULL;
444 }
445 }
446
mpd_server_get_version(MpdObj * mi)447 char *mpd_server_get_version(MpdObj *mi)
448 {
449 char *retval = NULL;
450 if(!mi || !mpd_check_connected(mi))
451 return NULL;
452 retval = malloc(10*sizeof(char));
453 snprintf(retval,10,"%i.%i.%i", mi->connection->version[0], mi->connection->version[1], mi->connection->version[2]);
454 /* always make sure the string is terminated */
455 retval[9] = '\0';
456 return retval;
457 }
458
mpd_server_get_allowed_commands(MpdObj * mi)459 int mpd_server_get_allowed_commands(MpdObj *mi)
460 {
461 char *temp = NULL;
462 int num_commands = 0;
463 if(!mi){
464 debug_printf(DEBUG_ERROR, "mi != NULL failed\n");
465 return MPD_ARGS_ERROR;
466 }
467 if(!mpd_check_connected(mi)) {
468 debug_printf(DEBUG_WARNING, "Not Connected");
469 return MPD_NOT_CONNECTED;
470 }
471 if(!mpd_server_check_version(mi,0,12,0)){
472 debug_printf(DEBUG_INFO, "Not supported by mpd");
473 return MPD_SERVER_NOT_SUPPORTED;
474 }
475
476 mpd_server_free_commands(mi);
477
478 if(mpd_lock_conn(mi))
479 {
480 debug_printf(DEBUG_ERROR, "lock failed");
481 return MPD_LOCK_FAILED;
482 }
483 mpd_sendCommandsCommand(mi->connection);
484 while((temp = mpd_getNextCommand(mi->connection)))
485 {
486 num_commands++;
487 mi->commands = realloc(mi->commands, (num_commands+1)*sizeof(MpdCommand));
488 mi->commands[num_commands-1].command_name = temp;
489 mi->commands[num_commands-1].enabled = TRUE;
490 mi->commands[num_commands].command_name = NULL;
491 mi->commands[num_commands].enabled = FALSE;
492 if(strcmp(mi->commands[num_commands-1].command_name, "idle") == 0) {
493 mi->has_idle = TRUE;
494 }
495 }
496 mpd_finishCommand(mi->connection);
497 mpd_sendNotCommandsCommand(mi->connection);
498 while((temp = mpd_getNextCommand(mi->connection)))
499 {
500 num_commands++;
501 mi->commands = realloc(mi->commands, (num_commands+1)*sizeof(MpdCommand));
502 mi->commands[num_commands-1].command_name = temp;
503 mi->commands[num_commands-1].enabled = FALSE;
504 mi->commands[num_commands].command_name = NULL;
505 mi->commands[num_commands].enabled = FALSE;
506 }
507 mpd_finishCommand(mi->connection);
508
509 if(mpd_unlock_conn(mi))
510 {
511 return MPD_LOCK_FAILED;
512 }
513 return MPD_OK;
514 }
515
516
517
mpd_disconnect(MpdObj * mi)518 int mpd_disconnect(MpdObj *mi)
519 {
520
521 /* lock */
522 mpd_lock_conn(mi);
523 debug_printf(DEBUG_INFO, "disconnecting\n");
524
525 if(mi->connection)
526 {
527 mpd_closeConnection(mi->connection);
528 mi->connection = NULL;
529 }
530 if(mi->status)
531 {
532 mpd_freeStatus(mi->status);
533 mi->status = NULL;
534 }
535 if(mi->stats)
536 {
537 mpd_freeStats(mi->stats);
538 mi->stats = NULL;
539 }
540 if(mi->CurrentSong)
541 {
542 mpd_freeSong(mi->CurrentSong);
543 mi->CurrentSong = NULL;
544 }
545 if(mi->url_handlers)
546 {
547 g_strfreev(mi->url_handlers);
548 mi->url_handlers = NULL;
549 }
550 mi->CurrentState.playlistid = -1;
551 mi->CurrentState.storedplaylistid = -1;
552 mi->CurrentState.state = -1;
553 mi->CurrentState.songid = -1;
554 mi->CurrentState.songpos = -1;
555 mi->CurrentState.nextsongid = -1;
556 mi->CurrentState.nextsongpos = -1;
557 mi->CurrentState.dbUpdateTime = 0;
558 mi->CurrentState.updatingDb = 0;
559 mi->CurrentState.repeat = -1;
560 mi->CurrentState.random = -1;
561 mi->CurrentState.volume = -2;
562 mi->CurrentState.xfade = -1;
563 mi->CurrentState.totaltime = 0;
564 mi->CurrentState.elapsedtime = 0;
565 mi->CurrentState.bitrate = 0;
566 mi->CurrentState.samplerate = 0;
567 mi->CurrentState.channels = 0;
568 mi->CurrentState.bits = 0;
569 mi->CurrentState.playlistLength = 0;
570 mi->CurrentState.error[0] = '\0';
571 /* search stuff */
572 mi->search_type = MPD_SEARCH_TYPE_NONE;
573 /* no need to initialize, but set it to anything anyway*/
574 mi->search_field = MPD_TAG_ITEM_ARTIST;
575 /* outputs */
576 mi->num_outputs = 0;
577 if(mi->output_states)
578 g_free(mi->output_states);
579 mi->output_states = NULL;
580
581 /* set 0 */
582 memset((mi->supported_tags), 0,sizeof(mi->supported_tags));
583
584 mi->has_idle = 0;
585
586 memcpy(&(mi->OldState), &(mi->CurrentState) , sizeof(MpdServerState));
587
588 mpd_free_queue_ob(mi);
589 mpd_server_free_commands(mi);
590 /*don't reset errors */
591 /* Remove this signal, we don't actually disconnect */
592 if(mi->connected)
593 {
594 /* set disconnect flag */
595 mi->connected = FALSE;
596
597 if(mi->the_connection_changed_callback != NULL)
598 {
599 mi->the_connection_changed_callback( mi, FALSE, mi->the_connection_changed_signal_userdata );
600 }
601 }
602 debug_printf(DEBUG_INFO, "Disconnect completed\n");
603 return MPD_OK;
604 }
mpd_connect(MpdObj * mi)605 int mpd_connect(MpdObj *mi)
606 {
607 return mpd_connect_real(mi,NULL);
608 }
mpd_connect_real(MpdObj * mi,mpd_Connection * connection)609 int mpd_connect_real(MpdObj *mi,mpd_Connection *connection)
610 {
611 int retv;
612 if(mi == NULL)
613 {
614 /* should return some spiffy error here */
615 debug_printf(DEBUG_ERROR, "mi != NULL failed");
616 return MPD_ARGS_ERROR;
617 }
618 /* reset errors */
619 mi->error = 0;
620 mi->error_mpd_code = 0;
621 if(mi->error_msg != NULL)
622 {
623 free(mi->error_msg);
624 }
625 mi->error_msg = NULL;
626
627 debug_printf(DEBUG_INFO, "connecting\n");
628 mpd_init_MpdServerState(&(mi->CurrentState));
629
630 memcpy(&(mi->OldState), &(mi->CurrentState), sizeof(MpdServerState));
631
632 if(mi->connected)
633 {
634 /* disconnect */
635 mpd_disconnect(mi);
636 }
637
638 if(mi->hostname == NULL)
639 {
640 mpd_set_hostname(mi, "localhost");
641 }
642 /* make sure this is locked */
643 if(!mi->connection_lock)
644 {
645 mpd_lock_conn(mi);
646 }
647 if(connection) {
648 mi->connection = connection;
649 } else {
650 /* make timeout configurable */
651 mi->connection = mpd_newConnection(mi->hostname,mi->port,mi->connection_timeout);
652 }
653 if(mi->connection == NULL)
654 {
655 /* TODO: make seperate error message? */
656 return MPD_NOT_CONNECTED;
657 }
658 if(mpd_check_error(mi) != MPD_OK)
659 {
660 /* TODO: make seperate error message? */
661 return MPD_NOT_CONNECTED;
662 }
663
664 /* set connected state */
665 mi->connected = TRUE;
666 if(mpd_unlock_conn(mi))
667 {
668 return MPD_LOCK_FAILED;
669 }
670
671 /* get the commands we are allowed to use */
672 retv = mpd_server_get_allowed_commands(mi);
673 if(retv!= MPD_OK)
674 {
675 return retv;
676 }
677 /* Trying to send password, this is needed to get right outputs and tag_types */
678 if(mi->password && strlen(mi->password) > 0)
679 {
680 mpd_send_password(mi);
681 }
682 else
683 {
684 /* Update tag types, this is done in send_password.
685 * mpd_send_password() does this.
686 * So only do it when no password is send.
687 */
688 int i;
689 char **retv = mpd_server_get_tag_types(mi);
690 if(retv){
691 for(i=0;i<MPD_TAG_ITEM_ANY;i++)
692 {
693 int j=0;
694 for(j=0;retv[j] && strcasecmp(retv[j],mpdTagItemKeys[i]); j++);
695 if(retv[j]) mi->supported_tags[i] = TRUE;
696 else mi->supported_tags[i] = FALSE;
697 }
698 g_strfreev(retv);
699 }
700 /* also always true */
701 mi->supported_tags[MPD_TAG_ITEM_FILENAME] = TRUE;
702 mi->supported_tags[MPD_TAG_ITEM_ANY] = TRUE;
703 }
704 /*
705
706 retv = mpd_server_update_outputs(mi);
707 if(retv != MPD_OK)
708 return retv;
709 */
710
711 retv = mpd_server_update_outputs(mi);
712 /** update the supported tags */
713 debug_printf(DEBUG_INFO, "Propagating connection changed");
714
715 if(mi->the_connection_changed_callback != NULL)
716 {
717 mi->the_connection_changed_callback( mi, TRUE, mi->the_connection_changed_signal_userdata );
718 }
719 /*
720 if(retv != MPD_OK)
721 return retv;
722 */
723 debug_printf(DEBUG_INFO, "Connected to mpd");
724 return MPD_OK;
725 }
726
mpd_check_connected(MpdObj * mi)727 int mpd_check_connected(MpdObj *mi)
728 {
729 if(mi == NULL)
730 {
731 return FALSE;
732 }
733 return mi->connected;
734 }
735
736
737 /* SIGNALS */
mpd_signal_connect_status_changed(MpdObj * mi,StatusChangedCallback status_changed,void * userdata)738 void mpd_signal_connect_status_changed (MpdObj *mi, StatusChangedCallback status_changed, void *userdata)
739 {
740 if(mi == NULL)
741 {
742 debug_printf(DEBUG_ERROR, "mi != NULL failed");
743 return;
744 }
745 mi->the_status_changed_callback = status_changed;
746 mi->the_status_changed_signal_userdata = userdata;
747 }
748
749
mpd_signal_connect_error(MpdObj * mi,ErrorCallback error_callback,void * userdata)750 void mpd_signal_connect_error(MpdObj *mi, ErrorCallback error_callback, void *userdata)
751 {
752 if(mi == NULL)
753 {
754 debug_printf(DEBUG_ERROR, "mi != NULL failed");
755 return;
756 }
757 mi->the_error_callback = error_callback;
758 mi->the_error_signal_userdata = userdata;
759 }
760
mpd_signal_connect_connection_changed(MpdObj * mi,ConnectionChangedCallback connection_changed,void * userdata)761 void mpd_signal_connect_connection_changed(MpdObj *mi, ConnectionChangedCallback connection_changed, void *userdata)
762 {
763 if(mi == NULL)
764 {
765 debug_printf(DEBUG_ERROR, "mi != NULL failed");
766 return;
767 }
768 mi->the_connection_changed_callback = connection_changed;
769 mi->the_connection_changed_signal_userdata = userdata;
770 }
771
772
773 /* more playlist */
774 /* MpdData Part */
mpd_new_data_struct(void)775 MpdData *mpd_new_data_struct(void)
776 {
777 return (MpdData*) g_slice_new0(MpdData_real);
778 }
779
mpd_new_data_struct_append(MpdData * data)780 MpdData *mpd_new_data_struct_append(MpdData * data)
781 {
782 MpdData_real *data_real = (MpdData_real*)data;
783 if(data_real == NULL)
784 {
785 data_real = (MpdData_real*)mpd_new_data_struct();
786 data_real->first = data_real;
787 }
788 else
789 {
790 data_real->next = (MpdData_real*)mpd_new_data_struct();
791 data_real->next->prev = data_real;
792 data_real = data_real->next;
793 data_real->next = NULL;
794 data_real->first = data_real->prev->first;
795 }
796 return (MpdData*)data_real;
797 }
798
mpd_data_get_first(MpdData const * const data)799 MpdData * mpd_data_get_first(MpdData const * const data)
800 {
801 MpdData_real const * const data_real = (MpdData_real const * const)data;
802 if(data_real != NULL)
803 {
804 return (MpdData*)data_real->first;
805 }
806 return NULL;
807 }
808
809
mpd_data_get_next(MpdData * const data)810 MpdData * mpd_data_get_next(MpdData * const data)
811 {
812 return mpd_data_get_next_real(data, TRUE);
813 }
814
mpd_data_get_next_real(MpdData * const data,int kill_list)815 MpdData * mpd_data_get_next_real(MpdData * const data, int kill_list)
816 {
817 MpdData_real *data_real = (MpdData_real*)data;
818 if (data_real != NULL)
819 {
820 if (data_real->next != NULL )
821 {
822 return (MpdData*)data_real->next;
823 }
824 else
825 {
826 if (kill_list) mpd_data_free((MpdData*)data_real);
827 return NULL;
828 }
829 }
830 return (MpdData*)data_real;
831 }
832
mpd_data_is_last(MpdData const * const data)833 int mpd_data_is_last(MpdData const * const data)
834 {
835 MpdData_real const * const data_real = (MpdData_real const * const)data;
836 if(data_real != NULL)
837 {
838 if (data_real->next == NULL)
839 {
840 return TRUE;
841 }
842 }
843 return FALSE;
844 }
845 /*
846 MpdData_head *mpd_data_get_head(MpdData const * const data) {
847 return ((MpdData_real*)data)->head;
848 }
849 */
mpd_data_concatenate(MpdData * const first,MpdData * const second)850 MpdData* mpd_data_concatenate( MpdData * const first, MpdData * const second)
851 {
852 MpdData_real *first_real = (MpdData_real*)first;
853 MpdData_real *second_real = (MpdData_real*)second;
854 MpdData_real *first_head = NULL;
855
856 if ( first == NULL ) {
857 if ( second != NULL )
858 return (MpdData*)second_real;
859 else
860 return NULL;
861 } else {
862 if ( second == NULL )
863 return (MpdData*)first_real;
864 }
865
866 first_head = (MpdData_real *)mpd_data_get_first(first);
867
868 /* find last element in first data list */
869 while (!mpd_data_is_last((MpdData*)first_real)) first_real = (MpdData_real*)mpd_data_get_next_real((MpdData*)first_real, FALSE);
870 second_real =(MpdData_real*) mpd_data_get_first((MpdData*)second_real);
871
872 first_real->next = second_real;
873 second_real->prev = first_real;
874
875 /* I need to set all the -> first correct */
876 while (second_real)
877 {
878 second_real->first = first_head;
879 second_real = (MpdData_real*)mpd_data_get_next_real((MpdData*)second_real, FALSE);
880 }
881
882 return (MpdData*)first_head;
883 }
884 /**
885 * Deletes an item from the list. It returns the next item in the list.
886 * if that is not available, it will return the last item
887 */
mpd_data_delete_item(MpdData * data)888 MpdData * mpd_data_delete_item(MpdData *data)
889 {
890 MpdData_real *temp = NULL, *data_real = (MpdData_real*)data;
891 if(data_real == NULL) return NULL;
892 /* if there is a next item, fix the prev pointer of the next item */
893 if (data_real->next)
894 {
895 data_real->next->prev = data_real->prev;
896 temp = data_real->next;
897 }
898 /* if there is a previous item, fix the next pointer of the previous item */
899 if (data_real->prev)
900 {
901 /* the next item of the previous is the next item of the current */
902 data_real->prev->next = data_real->next;
903 /* temp is the previous item */
904 temp = data_real->prev;
905 }
906
907 /* fix first, if removed item is the first */
908 if(temp && temp->first == data_real)
909 {
910 MpdData_real *first,*node = temp;
911 /* get first */
912 for(;node->prev;node = node->prev);
913 first = node;
914 while(node){
915 node->first = first;
916 node = node->next;
917 }
918 }
919 /* make the removed row a valid list, so I can use the default free function to free it */
920 data_real->next = NULL;
921 data_real->prev = NULL;
922 data_real->first = data_real;
923 /* free it */
924 mpd_data_free((MpdData *)data_real);
925
926 return (MpdData *)temp;
927 }
928
mpd_data_free(MpdData * data)929 void mpd_data_free(MpdData *data)
930 {
931 MpdData_real *data_real,*temp;
932 if(data == NULL)
933 {
934 debug_printf(DEBUG_ERROR, "data != NULL Failed");
935 return;
936 }
937 data_real = (MpdData_real *)mpd_data_get_first(data);
938 while(data_real){
939 temp = data_real;
940 if (data_real->type == MPD_DATA_TYPE_SONG) {
941 if(data_real->song) mpd_freeSong(data_real->song);
942 } else if (data_real->type == MPD_DATA_TYPE_OUTPUT_DEV) {
943 mpd_freeOutputElement(data_real->output_dev);
944 } else if(data_real->type == MPD_DATA_TYPE_DIRECTORY) {
945 if(data_real->directory)free(data_real->directory);
946 } else if(data_real->type == MPD_DATA_TYPE_PLAYLIST) {
947 if(data_real->playlist) mpd_freePlaylistFile(data_real->playlist);
948 } else {
949 free((void*)(data_real->tag));
950 }
951 if(data_real->freefunc)
952 {
953 if(data_real->userdata)
954 data_real->freefunc(data_real->userdata);
955 }
956 data_real = data_real->next;
957 g_slice_free(MpdData_real, temp);
958 }
959 }
960
961 /* clean this up.. make one while loop */
mpd_free_queue_ob(MpdObj * mi)962 static void mpd_free_queue_ob(MpdObj *mi)
963 {
964 MpdQueue *temp = NULL;
965 if(mi == NULL)
966 {
967 debug_printf(DEBUG_ERROR, "mi != NULL failed");
968 return;
969 }
970 if(mi->queue == NULL)
971 {
972 debug_printf(DEBUG_INFO, "mi->queue != NULL failed, nothing to clean.");
973 return;
974 }
975 mi->queue = mi->queue->first;
976 while(mi->queue != NULL)
977 {
978 temp = mi->queue->next;
979
980 if(mi->queue->path != NULL)
981 {
982 free(mi->queue->path);
983 }
984
985 g_slice_free(MpdQueue, mi->queue);
986 mi->queue = temp;
987 }
988 mi->queue = NULL;
989
990 }
991
mpd_new_queue_struct()992 MpdQueue *mpd_new_queue_struct()
993 {
994 return g_slice_new0(MpdQueue);
995 }
996
997
mpd_queue_get_next(MpdObj * mi)998 void mpd_queue_get_next(MpdObj *mi)
999 {
1000 if(mi->queue != NULL && mi->queue->next != NULL)
1001 {
1002 mi->queue = mi->queue->next;
1003 }
1004 else if(mi->queue->next == NULL)
1005 {
1006 mpd_free_queue_ob(mi);
1007 mi->queue = NULL;
1008 }
1009 }
1010
mpd_server_get_database_update_time(MpdObj * mi)1011 long unsigned mpd_server_get_database_update_time(MpdObj *mi)
1012 {
1013 if(!mpd_check_connected(mi))
1014 {
1015 debug_printf(DEBUG_WARNING,"not connected\n");
1016 return MPD_NOT_CONNECTED;
1017 }
1018 if(mpd_stats_check(mi) != MPD_OK)
1019 {
1020 debug_printf(DEBUG_WARNING,"Failed grabbing status\n");
1021 return MPD_STATS_FAILED;
1022 }
1023 return mi->stats->dbUpdateTime;
1024 }
1025
1026
mpd_server_get_output_devices(MpdObj * mi)1027 MpdData * mpd_server_get_output_devices(MpdObj *mi)
1028 {
1029 mpd_OutputEntity *output = NULL;
1030 MpdData *data = NULL;
1031 if(!mpd_check_connected(mi))
1032 {
1033 debug_printf(DEBUG_WARNING,"not connected\n");
1034 return NULL;
1035 }
1036 /* TODO: Check version */
1037 if(mpd_lock_conn(mi))
1038 {
1039 debug_printf(DEBUG_ERROR,"lock failed\n");
1040 return NULL;
1041 }
1042
1043 mpd_sendOutputsCommand(mi->connection);
1044 while (( output = mpd_getNextOutput(mi->connection)) != NULL)
1045 {
1046 data = mpd_new_data_struct_append(data);
1047 data->type = MPD_DATA_TYPE_OUTPUT_DEV;
1048 data->output_dev = output;
1049 }
1050 mpd_finishCommand(mi->connection);
1051
1052 /* unlock */
1053 if(mpd_unlock_conn(mi) != MPD_OK)
1054 {
1055 if(data)mpd_data_free(data);
1056 return NULL;
1057 }
1058 if(data == NULL)
1059 {
1060 return NULL;
1061 }
1062 return mpd_data_get_first(data);
1063 }
1064
mpd_server_set_output_device(MpdObj * mi,int device_id,int state)1065 int mpd_server_set_output_device(MpdObj *mi,int device_id,int state)
1066 {
1067 if(!mpd_check_connected(mi))
1068 {
1069 debug_printf(DEBUG_WARNING,"not connected\n");
1070 return MPD_NOT_CONNECTED;
1071 }
1072 if(mpd_lock_conn(mi))
1073 {
1074 debug_printf(DEBUG_ERROR,"lock failed\n");
1075 return MPD_LOCK_FAILED;
1076 }
1077 if(state)
1078 {
1079 mpd_sendEnableOutputCommand(mi->connection, device_id);
1080 }
1081 else
1082 {
1083 mpd_sendDisableOutputCommand(mi->connection, device_id);
1084 }
1085 mpd_finishCommand(mi->connection);
1086
1087 mpd_unlock_conn(mi);
1088 mpd_status_queue_update(mi);
1089 return FALSE;
1090 }
1091
mpd_server_check_version(MpdObj * mi,int major,int minor,int micro)1092 int mpd_server_check_version(MpdObj *mi, int major, int minor, int micro)
1093 {
1094 if(!mpd_check_connected(mi))
1095 {
1096 debug_printf(DEBUG_WARNING,"not connected\n");
1097 return FALSE;
1098 }
1099 if(major > mi->connection->version[0]) return FALSE;
1100 if(mi->connection->version[0] > major) return TRUE;
1101 if(minor > mi->connection->version[1]) return FALSE;
1102 if(mi->connection->version[1] > minor) return TRUE;
1103 if(micro > mi->connection->version[2]) return FALSE;
1104 if(mi->connection->version[2] > micro) return TRUE;
1105 return TRUE;
1106 }
1107
mpd_server_check_command_allowed(MpdObj * mi,const char * command)1108 int mpd_server_check_command_allowed(MpdObj *mi, const char *command)
1109 {
1110 int i;
1111 if(!mi || !command) return MPD_SERVER_COMMAND_ERROR;
1112 /* when we are connected to a mpd server that doesn't support commands and not commands
1113 * feature. (like mpd 0.11.5) allow everything
1114 */
1115 if(!mpd_server_check_version(mi, 0,12,0)) return MPD_SERVER_COMMAND_ALLOWED;
1116 /*
1117 * Also when somehow we failted to get commands
1118 */
1119 if(mi->commands == NULL) return MPD_SERVER_COMMAND_ALLOWED;
1120
1121
1122
1123 for(i=0;mi->commands[i].command_name;i++)
1124 {
1125 if(!strcasecmp(mi->commands[i].command_name, command))
1126 return mi->commands[i].enabled;
1127 }
1128 return MPD_SERVER_COMMAND_NOT_SUPPORTED;
1129 }
1130
mpd_server_get_url_handlers(MpdObj * mi)1131 char ** mpd_server_get_url_handlers(MpdObj *mi)
1132 {
1133 char *temp = NULL;
1134 int i=0;
1135 if(!mpd_check_connected(mi))
1136 {
1137 debug_printf(DEBUG_WARNING,"not connected\n");
1138 return FALSE;
1139 }
1140 if(mi->url_handlers){
1141 return g_strdupv(mi->url_handlers);
1142 }
1143 if(mpd_lock_conn(mi))
1144 {
1145 debug_printf(DEBUG_ERROR,"lock failed\n");
1146 return NULL;
1147 }
1148 /**
1149 * Fetch url handlers and store them
1150 */
1151 mpd_sendUrlHandlersCommand(mi->connection);
1152 while((temp = mpd_getNextHandler(mi->connection)) != NULL)
1153 {
1154 mi->url_handlers = realloc(mi->url_handlers,(i+2)*sizeof(*mi->url_handlers));
1155 mi->url_handlers[i] = temp;
1156 mi->url_handlers[i+1] = NULL;
1157 i++;
1158 }
1159 mpd_finishCommand(mi->connection);
1160
1161
1162 mpd_unlock_conn(mi);
1163 /* Return copy */
1164 return g_strdupv(mi->url_handlers);
1165 }
mpd_server_get_tag_types(MpdObj * mi)1166 char ** mpd_server_get_tag_types(MpdObj *mi)
1167 {
1168 char *temp = NULL;
1169 int i=0;
1170 char **retv = NULL;
1171 if(!mpd_check_connected(mi))
1172 {
1173 debug_printf(DEBUG_WARNING,"not connected\n");
1174 return FALSE;
1175 }
1176 if(mpd_lock_conn(mi))
1177 {
1178 debug_printf(DEBUG_ERROR,"lock failed\n");
1179 return NULL;
1180 }
1181 mpd_sendTagTypesCommand(mi->connection);
1182 while((temp = mpd_getNextTagType(mi->connection)) != NULL)
1183 {
1184 retv = realloc(retv,(i+2)*sizeof(*retv));
1185 retv[i] = temp;
1186 retv[i+1] = NULL;
1187 i++;
1188 }
1189 mpd_finishCommand(mi->connection);
1190
1191
1192 mpd_unlock_conn(mi);
1193 return retv;
1194 }
1195
mpd_misc_get_tag_by_name(char * name)1196 int mpd_misc_get_tag_by_name(char *name)
1197 {
1198 int i;
1199 if(name == NULL)
1200 {
1201 return MPD_ARGS_ERROR;
1202 }
1203 for(i=0; i < MPD_TAG_NUM_OF_ITEM_TYPES; i++)
1204 {
1205 if(!strcasecmp(mpdTagItemKeys[i], name))
1206 {
1207 return i;
1208 }
1209 }
1210 return MPD_TAG_NOT_FOUND;
1211 }
1212
mpd_server_update_outputs(MpdObj * mi)1213 int mpd_server_update_outputs(MpdObj *mi)
1214 {
1215 mpd_OutputEntity *output = NULL;
1216 if(!mpd_check_connected(mi))
1217 {
1218 debug_printf(DEBUG_WARNING,"not connected\n");
1219 return MPD_NOT_CONNECTED;
1220 }
1221 if(mpd_lock_conn(mi))
1222 {
1223 debug_printf(DEBUG_ERROR,"lock failed\n");
1224 return MPD_LOCK_FAILED;
1225 }
1226 mpd_sendOutputsCommand(mi->connection);
1227 while (( output = mpd_getNextOutput(mi->connection)) != NULL)
1228 {
1229 mi->num_outputs++;
1230 mi->output_states = realloc(mi->output_states,mi->num_outputs*sizeof(int));
1231 mi->output_states[mi->num_outputs-1] = FALSE;/*output->enabled;*/
1232 mpd_freeOutputElement(output);
1233 }
1234 mpd_finishCommand(mi->connection);
1235 return mpd_unlock_conn(mi);
1236 }
1237
mpd_server_has_idle(MpdObj * mi)1238 int mpd_server_has_idle(MpdObj *mi)
1239 {
1240 return mi->has_idle;
1241 }
1242
mpd_server_tag_supported(MpdObj * mi,int tag)1243 int mpd_server_tag_supported(MpdObj *mi, int tag)
1244 {
1245 if(!mi) return FALSE;
1246 if(tag < 0 || tag >= MPD_TAG_NUM_OF_ITEM_TYPES) {
1247 return FALSE;
1248 }
1249 return mi->supported_tags[tag];
1250 }
1251
mpd_server_set_replaygain_mode(MpdObj * mi,MpdServerReplaygainMode mode)1252 int mpd_server_set_replaygain_mode(MpdObj *mi, MpdServerReplaygainMode mode)
1253 {
1254 if(!mpd_check_connected(mi))
1255 {
1256 debug_printf(DEBUG_WARNING,"not connected\n");
1257 return MPD_NOT_CONNECTED;
1258 }
1259 if(mpd_lock_conn(mi))
1260 {
1261 debug_printf(DEBUG_ERROR,"lock failed\n");
1262 return MPD_LOCK_FAILED;
1263 }
1264 switch(mode){
1265 case MPD_SERVER_REPLAYGAIN_MODE_AUTO:
1266 mpd_sendSetReplayGainMode(mi->connection, "auto");
1267 break;
1268 case MPD_SERVER_REPLAYGAIN_MODE_TRACK:
1269 mpd_sendSetReplayGainMode(mi->connection, "track");
1270 break;
1271 case MPD_SERVER_REPLAYGAIN_MODE_ALBUM:
1272 mpd_sendSetReplayGainMode(mi->connection, "album");
1273 break;
1274 default:
1275 mpd_sendSetReplayGainMode(mi->connection, "off");
1276 break;
1277 }
1278 mpd_finishCommand(mi->connection);
1279 return mpd_unlock_conn(mi);
1280 }
1281
mpd_server_get_replaygain_mode(MpdObj * mi)1282 MpdServerReplaygainMode mpd_server_get_replaygain_mode(MpdObj *mi)
1283 {
1284 char *var;
1285 MpdServerReplaygainMode retv = MPD_SERVER_REPLAYGAIN_MODE_OFF;
1286 if(!mpd_check_connected(mi))
1287 {
1288 debug_printf(DEBUG_ERROR, "Not Connected\n");
1289 return retv;
1290 }
1291
1292 if(mpd_lock_conn(mi))
1293 {
1294 return retv;
1295 }
1296
1297 mpd_sendReplayGainModeCommand(mi->connection);
1298
1299 var = mpd_getReplayGainMode(mi->connection);
1300 if(var)
1301 {
1302 if(strcmp(var, "track")==0) {
1303 retv = MPD_SERVER_REPLAYGAIN_MODE_TRACK;
1304 }else if(strcmp(var, "album")==0) {
1305 retv = MPD_SERVER_REPLAYGAIN_MODE_ALBUM;
1306 }else if (strcmp(var, "auto") == 0) {
1307 retv = MPD_SERVER_REPLAYGAIN_MODE_AUTO;
1308 }
1309 free(var);
1310 }
1311 mpd_finishCommand(mi->connection);
1312
1313 mpd_unlock_conn(mi);
1314
1315 return retv;
1316 }
1317