# HG changeset patch # User Fabien Ninoles # Date 1211174936 14400 # Node ID 77136249e5ee9724cbbf224e94124e8ecfeaeb32 First import of mpd-client. diff -r 000000000000 -r 77136249e5ee .hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Mon May 19 01:28:56 2008 -0400 @@ -0,0 +1,5 @@ +^\.hg +^\.mq +syntax: glob +status +guards diff -r 000000000000 -r 77136249e5ee mpd-client --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mpd-client Mon May 19 01:28:56 2008 -0400 @@ -0,0 +1,3589 @@ +diff -r 171db9560cb5 clients/mpd/immsmpd.cc +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/clients/mpd/immsmpd.cc Mon May 19 00:21:51 2008 -0400 +@@ -0,0 +1,282 @@ ++#include "immsutil.h" ++#include "clientstub.h" ++ ++#include ++#include ++ ++#include "mpdinterface.h" ++#include ++ ++ ++using std::endl; ++using std::list; ++using namespace mpd_interface; ++ ++// interface for communication with IMMS server ++// comes from clientstub.h ++struct MPDOps; ++typedef IMMSClient MPDClient; ++ ++int poll_time = 250; // miliseconds ++const double reconnect_interval = 1.0; // seconds ++GMainLoop *loop = nullptr; ++GSource* ts; ++ ++MPDClient imms; ++Player mpd; ++list playqueue; ++const list::size_type pq_capacity = 1; ++ ++ ++// that's the main function - it's called regularly and handles all the ++// IMMS and MPD communication and decides what to do when anything happens ++gboolean do_events(void *unused); ++ ++// determines whether the currently playing song has been selected ++// specifically by the user ++bool next_jumped(); ++ ++void notify_song_ended(const Song& song, double elapsed_time, bool jumped); ++ ++// resets the elapsed_timer and sets the jumped flag, too ++void notify_song_started(const Song& song, double& elapsed_time, ++ bool& jumped); ++ ++void update_playqueue(); ++bool playqueue_contains(int song); ++ ++void quit(int signum); ++ ++ ++struct MPDOps ++{ ++ // IMMS server's suggestion of the next song ++ static void set_next(int next) ++ { ++ if(playqueue.size() < pq_capacity) { ++ if(mpd.song(current).path() == mpd.song(next).path() || ++ mpd.song(previous).path() == mpd.song(next).path() || ++ playqueue_contains(next) || next >= mpd.playlist_length()) { ++ update_playqueue(); ++ } ++ else { ++ playqueue.push_back(next); ++ } ++ } ++ } ++ // no idea, works just peach as it is ++ // asked mag about it, waiting for an answer ++ // TODO if he doesn't answer, dig it out ++ static void reset_selection() { } ++ // path of the song at the given pos for IMMS server ++ static string get_item(int index) {return mpd.song(index).path(); } ++ // size of the playlist for the IMMS server ++ static int get_length() { return mpd.playlist_length(); } ++}; ++ ++int main(int argc, char **argv) ++{ ++ // glib initialization ++ loop = g_main_loop_new(nullptr, FALSE); ++ ++ signal(SIGINT, quit); ++ signal(SIGTERM, quit); ++ signal(SIGPIPE, SIG_IGN); ++ ++ ts = g_timeout_source_new(poll_time); ++ g_source_attach(ts, nullptr); ++ g_source_set_callback(ts, (GSourceFunc)do_events, nullptr, nullptr); ++ // end of glib init ++ ++ g_main_loop_run(loop); ++ ++ LOG(INFO) << "Exitting." << endl; ++ return 0; ++} ++ ++gboolean do_events(void *unused) ++{ ++ static const double time_inc = double(poll_time)/1000.0; ++ // the time that ACTUALLY elapsed when playing the song (in seconds) ++ static double elapsed_time = 0; ++ static bool jumped = false; ++ static double reconnect_timeout = 0; ++ ++ try { ++ if(!mpd.connected()) { ++ if(reconnect_timeout < time_inc) { ++ LOG(INFO) << "Not connected to MPD. Attempting to connect..." ++ << endl; ++ reconnect_timeout = reconnect_interval; ++ mpd.connect(); ++ LOG(INFO) << "Connected to MPD." << endl; ++ reconnect_timeout = 0; ++ } ++ else { ++ reconnect_timeout -= time_inc; ++ return TRUE; ++ } ++ } ++ if(imms.check_connection()) imms.setup(0); ++ if(!imms.isok()) return TRUE; ++ ++ mpd.refresh(); ++ ++ if(mpd.playlist_changed()) { ++ imms.playlist_changed(mpd.playlist_length()); ++ playqueue.clear(); ++ } ++ if(mpd.radnom()) update_playqueue(); ++ ++ if(mpd.status_changed()) { ++ if(mpd.status(current) == stopped) { ++ notify_song_ended(mpd.song(current), elapsed_time, jumped); ++ } ++ else if(mpd.status(previous) == stopped && ++ mpd.status(current)==playing) { ++ notify_song_started(mpd.song(current), elapsed_time, jumped); ++ ++ // clear the song_changed() flag ++ if(mpd.song_changed()) mpd.refresh(); ++ } ++ } ++ if(mpd.status(current) == playing) { ++ elapsed_time += time_inc; ++ if(mpd.song_changed()) { ++ notify_song_ended(mpd.song(previous), elapsed_time, jumped); ++ ++ if(!mpd.radnom() || playqueue.empty()) { ++ notify_song_started(mpd.song(current), elapsed_time, jumped); ++ } ++ else { ++ mpd.play_song(playqueue.front()); ++ notify_song_started(mpd.song(playqueue.front()), elapsed_time, ++ jumped); ++ playqueue.erase(playqueue.begin()); ++ update_playqueue(); ++ // this could otherwise incorrectly set song_changed() flag ++ mpd.refresh(); ++ } ++ } ++ else { ++ // fix the real timer if it's inconsistent with what player ++ // says is the elapsed time of the current song ++ if(elapsed_time - double(mpd.song(current).elapsed()) >= 3.0) { ++ if(mpd.song(current).elapsed() == 0) { ++ Song tmp_ended = mpd.song(current); ++ tmp_ended.set_length(int(elapsed_time) * 2); ++ notify_song_ended(tmp_ended, elapsed_time, jumped); ++ notify_song_started(mpd.song(current), elapsed_time, jumped); ++ jumped = false; ++ } ++ elapsed_time = mpd.song(current).elapsed(); ++ } ++ } ++ } ++ } ++ catch (connection_err) { ++ if(reconnect_timeout >= reconnect_interval) { ++ LOG(INFO) << "Connecting to MPD was unsuccessful. " ++ "Next attempt in " << reconnect_interval << " second" ++ << (reconnect_interval == 1.0 ? "":"s") << "." << endl; ++ } ++ else { ++ LOG(INFO) << "Disconnected from MPD." << endl; ++ } ++ } ++ catch (mpd_err ex) { ++ LOG(ERROR) << "Error: " << ex.message() << endl; ++ g_main_quit(loop); ++ loop = nullptr; ++ exit(1); ++ } ++ return TRUE; ++} ++ ++// determines whether the song has been played whole ++bool played_whole(double elapsed_time, int length) ++{ ++ static const double abs_limit = 20.0; ++ static const double coeficient = 0.08; ++ ++ double rel_limit = double(length) * coeficient; ++ ++ double limit = (abs_limit < rel_limit) ? abs_limit : rel_limit; ++ return double(length)-elapsed_time < limit; ++} ++// determines whether the song has been skipped early ++// (thus being set as not a prefed one) ++bool bad(double elapsed_time, int length) ++{ ++ static const double abs_limit = 30.0; ++ static const double coeficient = 0.13; ++ ++ double rel_limit = double(length) * coeficient; ++ ++ double limit = (rel_limit < abs_limit) ? rel_limit : abs_limit; ++ return elapsed_time < limit; ++} ++bool next_jumped() ++{ ++ if(mpd.radnom()) return false; ++ ++ if(mpd.song(current).pos() == mpd.song(previous).pos()+1) ++ return false; ++ if(mpd.song(current).pos() == 0 && ++ mpd.song(previous).pos() == mpd.playlist_length()-1) ++ return false; ++ ++ return true; ++} ++void notify_song_ended(const Song& song, double elapsed_time, bool jumped) ++{ ++ if(song.length() == Song::invalid_time) return; ++ ++ imms.end_song(played_whole(elapsed_time, song.length()), jumped, ++ bad(elapsed_time, song.length()) ); ++} ++ ++void notify_song_started(const Song& song, double& elapsed_timer, ++ bool& jumped) ++{ ++ imms.start_song(song.pos(), song.path()); ++ elapsed_timer = 0; ++ jumped = next_jumped(); ++ mpd.song_changed(); // clear the change notification ++} ++ ++void update_playqueue() ++{ ++ static const int timeout_value = 3; ++ static int timeout = 0; ++ ++ if(timeout == 0) ++ { ++ if(playqueue.size() < pq_capacity) { ++ list::size_type i; ++ for(i = pq_capacity - playqueue.size(); i!=0; i--) { ++ imms.select_next(); ++ } ++ timeout = timeout_value; ++ } ++ } ++ else --timeout; ++} ++ ++bool playqueue_contains(int song) ++{ ++ list::const_iterator i; ++ for(i = playqueue.begin(); i!= playqueue.end(); i++) { ++ if (*i == song) return true; ++ } ++ return false; ++} ++ ++void quit(int signum) ++{ ++ if (loop) g_main_quit(loop); ++ loop = nullptr; ++ signal(signum, SIG_DFL); ++} ++ ++ +diff -r 171db9560cb5 clients/mpd/libmpdclient.c +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/clients/mpd/libmpdclient.c Mon May 19 00:21:51 2008 -0400 +@@ -0,0 +1,1955 @@ ++/* libmpdclient ++ (c)2003-2006 by Warren Dukes (warren.dukes@gmail.com) ++ This project's homepage is: http://www.musicpd.org ++ ++ Redistribution and use in source and binary forms, with or without ++ modification, are permitted provided that the following conditions ++ are met: ++ ++ - Redistributions of source code must retain the above copyright ++ notice, this list of conditions and the following disclaimer. ++ ++ - Redistributions in binary form must reproduce the above copyright ++ notice, this list of conditions and the following disclaimer in the ++ documentation and/or other materials provided with the distribution. ++ ++ - Neither the name of the Music Player Daemon nor the names of its ++ contributors may be used to endorse or promote products derived from ++ this software without specific prior written permission. ++ ++ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ++ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ++ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ++ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR ++ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, ++ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, ++ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR ++ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF ++ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING ++ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++*/ ++ ++#include "libmpdclient.h" ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#ifdef WIN32 ++# include ++# include ++#else ++# include ++# include ++# include ++# include ++#endif ++ ++/* (bits+1)/3 (plus the sign character) */ ++#define INTLEN ((sizeof(int) * CHAR_BIT + 1) / 3 + 1) ++#define LONGLONGLEN ((sizeof(long long) * CHAR_BIT + 1) / 3 + 1) ++ ++#define COMMAND_LIST 1 ++#define COMMAND_LIST_OK 2 ++ ++#ifndef MPD_NO_GAI ++# ifdef AI_ADDRCONFIG ++# define MPD_HAVE_GAI ++# endif ++#endif ++ ++#ifndef MSG_DONTWAIT ++# define MSG_DONTWAIT 0 ++#endif ++ ++#ifdef WIN32 ++# define SELECT_ERRNO_IGNORE (errno == WSAEINTR || errno == WSAEINPROGRESS) ++# define SENDRECV_ERRNO_IGNORE SELECT_ERRNO_IGNORE ++#else ++# define SELECT_ERRNO_IGNORE (errno == EINTR) ++# define SENDRECV_ERRNO_IGNORE (errno == EINTR || errno == EAGAIN) ++# define winsock_dll_error(c) 0 ++# define closesocket(s) close(s) ++# define WSACleanup() do { /* nothing */ } while (0) ++#endif ++ ++#ifdef WIN32 ++static int winsock_dll_error(mpd_Connection *connection) ++{ ++ WSADATA wsaData; ++ if ((WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0 || ++ LOBYTE(wsaData.wVersion) != 2 || ++ HIBYTE(wsaData.wVersion) != 2 ) { ++ strcpy(connection->errorStr, ++ "Could not find usable WinSock DLL."); ++ connection->error = MPD_ERROR_SYSTEM; ++ return 1; ++ } ++ return 0; ++} ++ ++static int do_connect_fail(mpd_Connection *connection, ++ const struct sockaddr *serv_addr, int addrlen) ++{ ++ int iMode = 1; /* 0 = blocking, else non-blocking */ ++ ioctlsocket(connection->sock, FIONBIO, (u_long FAR*) &iMode); ++ return (connect(connection->sock,serv_addr,addrlen) == SOCKET_ERROR ++ && WSAGetLastError() != WSAEWOULDBLOCK); ++} ++#else /* !WIN32 (sane operating systems) */ ++static int do_connect_fail(mpd_Connection *connection, ++ const struct sockaddr *serv_addr, int addrlen) ++{ ++ int flags = fcntl(connection->sock, F_GETFL, 0); ++ fcntl(connection->sock, F_SETFL, flags | O_NONBLOCK); ++ return (connect(connection->sock,serv_addr,addrlen)<0 && ++ errno!=EINPROGRESS); ++} ++#endif /* !WIN32 */ ++ ++#ifdef MPD_HAVE_GAI ++static int mpd_connect(mpd_Connection * connection, const char * host, int port, ++ float timeout) ++{ ++ int error; ++ char service[INTLEN+1]; ++ struct addrinfo hints; ++ struct addrinfo *res = NULL; ++ struct addrinfo *addrinfo = NULL; ++ ++ /** ++ * Setup hints ++ */ ++ hints.ai_flags = AI_ADDRCONFIG; ++ hints.ai_family = PF_UNSPEC; ++ hints.ai_socktype = SOCK_STREAM; ++ hints.ai_protocol = IPPROTO_TCP; ++ hints.ai_addrlen = 0; ++ hints.ai_addr = NULL; ++ hints.ai_canonname = NULL; ++ hints.ai_next = NULL; ++ ++ snprintf(service, sizeof(service), "%i", port); ++ ++ error = getaddrinfo(host, service, &hints, &addrinfo); ++ ++ if (error) { ++ snprintf(connection->errorStr, MPD_ERRORSTR_MAX_LENGTH, ++ "host \"%s\" not found: %s", ++ host, gai_strerror(error)); ++ connection->error = MPD_ERROR_UNKHOST; ++ return -1; ++ } ++ ++ for (res = addrinfo; res; res = res->ai_next) { ++ /* create socket */ ++ connection->sock = socket(res->ai_family, SOCK_STREAM, ++ res->ai_protocol); ++ if (connection->sock < 0) { ++ snprintf(connection->errorStr, MPD_ERRORSTR_MAX_LENGTH, ++ "problems creating socket: %s", ++ strerror(errno)); ++ connection->error = MPD_ERROR_SYSTEM; ++ freeaddrinfo(addrinfo); ++ return -1; ++ } ++ ++ mpd_setConnectionTimeout(connection, timeout); ++ ++ /* connect stuff */ ++ if (do_connect_fail(connection, ++ res->ai_addr, res->ai_addrlen)) { ++ /* try the next address family */ ++ closesocket(connection->sock); ++ connection->sock = -1; ++ continue; ++ } ++ } ++ ++ freeaddrinfo(addrinfo); ++ ++ if (connection->sock < 0) { ++ snprintf(connection->errorStr, MPD_ERRORSTR_MAX_LENGTH, ++ "problems connecting to \"%s\" on port %i: %s", ++ host, port, strerror(errno)); ++ connection->error = MPD_ERROR_CONNPORT; ++ ++ return -1; ++ } ++ ++ return 0; ++} ++#else /* !MPD_HAVE_GAI */ ++static int mpd_connect(mpd_Connection * connection, const char * host, int port, ++ float timeout) ++{ ++ struct hostent * he; ++ struct sockaddr * dest; ++ int destlen; ++ struct sockaddr_in sin; ++ ++ if(!(he=gethostbyname(host))) { ++ snprintf(connection->errorStr,MPD_ERRORSTR_MAX_LENGTH, ++ "host \"%s\" not found",host); ++ connection->error = MPD_ERROR_UNKHOST; ++ return -1; ++ } ++ ++ memset(&sin,0,sizeof(struct sockaddr_in)); ++ /*dest.sin_family = he->h_addrtype;*/ ++ sin.sin_family = AF_INET; ++ sin.sin_port = htons(port); ++ ++ switch(he->h_addrtype) { ++ case AF_INET: ++ memcpy((char *)&sin.sin_addr.s_addr,(char *)he->h_addr, ++ he->h_length); ++ dest = (struct sockaddr *)&sin; ++ destlen = sizeof(struct sockaddr_in); ++ break; ++ default: ++ strcpy(connection->errorStr,"address type is not IPv4"); ++ connection->error = MPD_ERROR_SYSTEM; ++ return -1; ++ break; ++ } ++ ++ if((connection->sock = socket(dest->sa_family,SOCK_STREAM,0))<0) { ++ strcpy(connection->errorStr,"problems creating socket"); ++ connection->error = MPD_ERROR_SYSTEM; ++ return -1; ++ } ++ ++ mpd_setConnectionTimeout(connection,timeout); ++ ++ /* connect stuff */ ++ if (do_connect_fail(connection, dest, destlen)) { ++ snprintf(connection->errorStr,MPD_ERRORSTR_MAX_LENGTH, ++ "problems connecting to \"%s\" on port" ++ " %i",host,port); ++ connection->error = MPD_ERROR_CONNPORT; ++ return -1; ++ } ++ ++ return 0; ++} ++#endif /* !MPD_HAVE_GAI */ ++ ++char * mpdTagItemKeys[MPD_TAG_NUM_OF_ITEM_TYPES] = ++{ ++ "Artist", ++ "Album", ++ "Title", ++ "Track", ++ "Name", ++ "Genre", ++ "Date", ++ "Composer", ++ "Performer", ++ "Comment", ++ "Disc", ++ "Filename", ++ "Any" ++}; ++ ++static char * mpd_sanitizeArg(const char * arg) { ++ size_t i; ++ char * ret; ++ register const char *c; ++ register char *rc; ++ ++ /* instead of counting in that loop above, just ++ * use a bit more memory and half running time ++ */ ++ ret = (char *)malloc(strlen(arg) * 2 + 1); ++ ++ c = arg; ++ rc = ret; ++ for(i = strlen(arg)+1; i != 0; --i) { ++ if(*c=='"' || *c=='\\') ++ *rc++ = '\\'; ++ *(rc++) = *(c++); ++ } ++ ++ return ret; ++} ++ ++static mpd_ReturnElement * mpd_newReturnElement(const char * name, const char * value) ++{ ++ mpd_ReturnElement * ret = (mpd_ReturnElement *)malloc(sizeof(mpd_ReturnElement)); ++ ++ ret->name = strdup(name); ++ ret->value = strdup(value); ++ ++ return ret; ++} ++ ++static void mpd_freeReturnElement(mpd_ReturnElement * re) { ++ free(re->name); ++ free(re->value); ++ free(re); ++} ++ ++void mpd_setConnectionTimeout(mpd_Connection * connection, float timeout) { ++ connection->timeout.tv_sec = (int)timeout; ++ connection->timeout.tv_usec = (int)(timeout*1e6 - ++ connection->timeout.tv_sec*1000000 + ++ 0.5); ++} ++ ++static int mpd_parseWelcome(mpd_Connection * connection, const char * host, int port, ++ char * rt, char * output) { ++ char * tmp; ++ char * test; ++ int i; ++ ++ if(strncmp(output,MPD_WELCOME_MESSAGE,strlen(MPD_WELCOME_MESSAGE))) { ++ snprintf(connection->errorStr,MPD_ERRORSTR_MAX_LENGTH, ++ "mpd not running on port %i on host \"%s\"", ++ port,host); ++ connection->error = MPD_ERROR_NOTMPD; ++ return 1; ++ } ++ ++ tmp = &output[strlen(MPD_WELCOME_MESSAGE)]; ++ ++ for(i=0;i<3;i++) { ++ if(tmp) connection->version[i] = strtol(tmp,&test,10); ++ ++ if (!tmp || (test[0] != '.' && test[0] != '\0')) { ++ snprintf(connection->errorStr, ++ MPD_ERRORSTR_MAX_LENGTH, ++ "error parsing version number at " ++ "\"%s\"", ++ &output[strlen(MPD_WELCOME_MESSAGE)]); ++ connection->error = MPD_ERROR_NOTMPD; ++ return 1; ++ } ++ tmp = ++test; ++ } ++ ++ return 0; ++} ++ ++mpd_Connection * mpd_newConnection(const char * host, int port, float timeout) { ++ int err; ++ char * rt; ++ char * output = NULL; ++ mpd_Connection * connection = (mpd_Connection *)malloc(sizeof(mpd_Connection)); ++ struct timeval tv; ++ fd_set fds; ++ strcpy(connection->buffer,""); ++ connection->buflen = 0; ++ connection->bufstart = 0; ++ strcpy(connection->errorStr,""); ++ connection->error = 0; ++ connection->doneProcessing = 0; ++ connection->commandList = 0; ++ connection->listOks = 0; ++ connection->doneListOk = 0; ++ connection->returnElement = NULL; ++ connection->request = NULL; ++ ++ if (winsock_dll_error(connection)) ++ return connection; ++ ++ if (mpd_connect(connection, host, port, timeout) < 0) ++ return connection; ++ ++ while(!(rt = strstr(connection->buffer,"\n"))) { ++ tv.tv_sec = connection->timeout.tv_sec; ++ tv.tv_usec = connection->timeout.tv_usec; ++ FD_ZERO(&fds); ++ FD_SET(connection->sock,&fds); ++ if((err = select(connection->sock+1,&fds,NULL,NULL,&tv)) == 1) { ++ int readed; ++ readed = recv(connection->sock, ++ &(connection->buffer[connection->buflen]), ++ MPD_BUFFER_MAX_LENGTH-connection->buflen,0); ++ if(readed<=0) { ++ snprintf(connection->errorStr,MPD_ERRORSTR_MAX_LENGTH, ++ "problems getting a response from" ++ " \"%s\" on port %i : %s",host, ++ port, strerror(errno)); ++ connection->error = MPD_ERROR_NORESPONSE; ++ return connection; ++ } ++ connection->buflen+=readed; ++ connection->buffer[connection->buflen] = '\0'; ++ } ++ else if(err<0) { ++ if (SELECT_ERRNO_IGNORE) ++ continue; ++ snprintf(connection->errorStr, ++ MPD_ERRORSTR_MAX_LENGTH, ++ "problems connecting to \"%s\" on port" ++ " %i",host,port); ++ connection->error = MPD_ERROR_CONNPORT; ++ return connection; ++ } ++ else { ++ snprintf(connection->errorStr,MPD_ERRORSTR_MAX_LENGTH, ++ "timeout in attempting to get a response from" ++ " \"%s\" on port %i",host,port); ++ connection->error = MPD_ERROR_NORESPONSE; ++ return connection; ++ } ++ } ++ ++ *rt = '\0'; ++ output = strdup(connection->buffer); ++ strcpy(connection->buffer,rt+1); ++ connection->buflen = strlen(connection->buffer); ++ ++ if(mpd_parseWelcome(connection,host,port,rt,output) == 0) connection->doneProcessing = 1; ++ ++ free(output); ++ ++ return connection; ++} ++ ++void mpd_clearError(mpd_Connection * connection) { ++ connection->error = 0; ++ connection->errorStr[0] = '\0'; ++} ++ ++void mpd_closeConnection(mpd_Connection * connection) { ++ closesocket(connection->sock); ++ if(connection->returnElement) free(connection->returnElement); ++ if(connection->request) free(connection->request); ++ free(connection); ++ WSACleanup(); ++} ++ ++static void mpd_executeCommand(mpd_Connection * connection, char * command) { ++ int ret; ++ struct timeval tv; ++ fd_set fds; ++ char * commandPtr = command; ++ int commandLen = strlen(command); ++ ++ if(!connection->doneProcessing && !connection->commandList) { ++ strcpy(connection->errorStr,"not done processing current command"); ++ connection->error = 1; ++ return; ++ } ++ ++ mpd_clearError(connection); ++ ++ FD_ZERO(&fds); ++ FD_SET(connection->sock,&fds); ++ tv.tv_sec = connection->timeout.tv_sec; ++ tv.tv_usec = connection->timeout.tv_usec; ++ ++ while((ret = select(connection->sock+1,NULL,&fds,NULL,&tv)==1) || ++ (ret==-1 && SELECT_ERRNO_IGNORE)) { ++ ret = send(connection->sock,commandPtr,commandLen,MSG_DONTWAIT); ++ if(ret<=0) ++ { ++ if (SENDRECV_ERRNO_IGNORE) continue; ++ snprintf(connection->errorStr,MPD_ERRORSTR_MAX_LENGTH, ++ "problems giving command \"%s\"",command); ++ connection->error = MPD_ERROR_SENDING; ++ return; ++ } ++ else { ++ commandPtr+=ret; ++ commandLen-=ret; ++ } ++ ++ if(commandLen<=0) break; ++ } ++ ++ if(commandLen>0) { ++ perror(""); ++ snprintf(connection->errorStr,MPD_ERRORSTR_MAX_LENGTH, ++ "timeout sending command \"%s\"",command); ++ connection->error = MPD_ERROR_TIMEOUT; ++ return; ++ } ++ ++ if(!connection->commandList) connection->doneProcessing = 0; ++ else if(connection->commandList == COMMAND_LIST_OK) { ++ connection->listOks++; ++ } ++} ++ ++static void mpd_getNextReturnElement(mpd_Connection * connection) { ++ char * output = NULL; ++ char * rt = NULL; ++ char * name = NULL; ++ char * value = NULL; ++ fd_set fds; ++ struct timeval tv; ++ char * tok = NULL; ++ int readed; ++ char * bufferCheck = NULL; ++ int err; ++ int pos; ++ ++ if(connection->returnElement) mpd_freeReturnElement(connection->returnElement); ++ connection->returnElement = NULL; ++ ++ if(connection->doneProcessing || (connection->listOks && ++ connection->doneListOk)) ++ { ++ strcpy(connection->errorStr,"already done processing current command"); ++ connection->error = 1; ++ return; ++ } ++ ++ bufferCheck = connection->buffer+connection->bufstart; ++ while(connection->bufstart>=connection->buflen || ++ !(rt = strchr(bufferCheck,'\n'))) { ++ if(connection->buflen>=MPD_BUFFER_MAX_LENGTH) { ++ memmove(connection->buffer, ++ connection->buffer+ ++ connection->bufstart, ++ connection->buflen- ++ connection->bufstart+1); ++ connection->buflen-=connection->bufstart; ++ connection->bufstart = 0; ++ } ++ if(connection->buflen>=MPD_BUFFER_MAX_LENGTH) { ++ strcpy(connection->errorStr,"buffer overrun"); ++ connection->error = MPD_ERROR_BUFFEROVERRUN; ++ connection->doneProcessing = 1; ++ connection->doneListOk = 0; ++ return; ++ } ++ bufferCheck = connection->buffer+connection->buflen; ++ tv.tv_sec = connection->timeout.tv_sec; ++ tv.tv_usec = connection->timeout.tv_usec; ++ FD_ZERO(&fds); ++ FD_SET(connection->sock,&fds); ++ if((err = select(connection->sock+1,&fds,NULL,NULL,&tv) == 1)) { ++ readed = recv(connection->sock, ++ connection->buffer+connection->buflen, ++ MPD_BUFFER_MAX_LENGTH-connection->buflen, ++ MSG_DONTWAIT); ++ if(readed<0 && SENDRECV_ERRNO_IGNORE) { ++ continue; ++ } ++ if(readed<=0) { ++ strcpy(connection->errorStr,"connection" ++ " closed"); ++ connection->error = MPD_ERROR_CONNCLOSED; ++ connection->doneProcessing = 1; ++ connection->doneListOk = 0; ++ return; ++ } ++ connection->buflen+=readed; ++ connection->buffer[connection->buflen] = '\0'; ++ } ++ else if(err<0 && SELECT_ERRNO_IGNORE) continue; ++ else { ++ strcpy(connection->errorStr,"connection timeout"); ++ connection->error = MPD_ERROR_TIMEOUT; ++ connection->doneProcessing = 1; ++ connection->doneListOk = 0; ++ return; ++ } ++ } ++ ++ *rt = '\0'; ++ output = connection->buffer+connection->bufstart; ++ connection->bufstart = rt - connection->buffer + 1; ++ ++ if(strcmp(output,"OK")==0) { ++ if(connection->listOks > 0) { ++ strcpy(connection->errorStr, "expected more list_OK's"); ++ connection->error = 1; ++ } ++ connection->listOks = 0; ++ connection->doneProcessing = 1; ++ connection->doneListOk = 0; ++ return; ++ } ++ ++ if(strcmp(output, "list_OK") == 0) { ++ if(!connection->listOks) { ++ strcpy(connection->errorStr, ++ "got an unexpected list_OK"); ++ connection->error = 1; ++ } ++ else { ++ connection->doneListOk = 1; ++ connection->listOks--; ++ } ++ return; ++ } ++ ++ if(strncmp(output,"ACK",strlen("ACK"))==0) { ++ char * test; ++ char * needle; ++ int val; ++ ++ strcpy(connection->errorStr, output); ++ connection->error = MPD_ERROR_ACK; ++ connection->errorCode = MPD_ACK_ERROR_UNK; ++ connection->errorAt = MPD_ERROR_AT_UNK; ++ connection->doneProcessing = 1; ++ connection->doneListOk = 0; ++ ++ needle = strchr(output, '['); ++ if(!needle) return; ++ val = strtol(needle+1, &test, 10); ++ if(*test != '@') return; ++ connection->errorCode = val; ++ val = strtol(test+1, &test, 10); ++ if(*test != ']') return; ++ connection->errorAt = val; ++ return; ++ } ++ ++ tok = strchr(output, ':'); ++ if (!tok) return; ++ pos = tok - output; ++ value = ++tok; ++ name = output; ++ name[pos] = '\0'; ++ ++ if(value[0]==' ') { ++ connection->returnElement = mpd_newReturnElement(name,&(value[1])); ++ } ++ else { ++ snprintf(connection->errorStr,MPD_ERRORSTR_MAX_LENGTH, ++ "error parsing: %s:%s",name,value); ++ connection->error = 1; ++ } ++} ++ ++void mpd_finishCommand(mpd_Connection * connection) { ++ while(!connection->doneProcessing) { ++ if(connection->doneListOk) connection->doneListOk = 0; ++ mpd_getNextReturnElement(connection); ++ } ++} ++ ++static void mpd_finishListOkCommand(mpd_Connection * connection) { ++ while(!connection->doneProcessing && connection->listOks && ++ !connection->doneListOk) ++ { ++ mpd_getNextReturnElement(connection); ++ } ++} ++ ++int mpd_nextListOkCommand(mpd_Connection * connection) { ++ mpd_finishListOkCommand(connection); ++ if(!connection->doneProcessing) connection->doneListOk = 0; ++ if(connection->listOks == 0 || connection->doneProcessing) return -1; ++ return 0; ++} ++ ++void mpd_sendStatusCommand(mpd_Connection * connection) { ++ mpd_executeCommand(connection,"status\n"); ++} ++ ++mpd_Status * mpd_getStatus(mpd_Connection * connection) { ++ mpd_Status * status; ++ ++ /*mpd_executeCommand(connection,"status\n"); ++ ++ if(connection->error) return NULL;*/ ++ ++ if(connection->doneProcessing || (connection->listOks && ++ connection->doneListOk)) ++ { ++ return NULL; ++ } ++ ++ if(!connection->returnElement) mpd_getNextReturnElement(connection); ++ ++ status = (mpd_Status *)malloc(sizeof(mpd_Status)); ++ status->volume = -1; ++ status->repeat = 0; ++ status->random = 0; ++ status->playlist = -1; ++ status->playlistLength = -1; ++ status->state = -1; ++ status->song = 0; ++ status->songid = 0; ++ status->elapsedTime = 0; ++ status->totalTime = 0; ++ status->bitRate = 0; ++ status->sampleRate = 0; ++ status->bits = 0; ++ status->channels = 0; ++ status->crossfade = -1; ++ status->error = NULL; ++ status->updatingDb = 0; ++ ++ if(connection->error) { ++ free(status); ++ return NULL; ++ } ++ while(connection->returnElement) { ++ mpd_ReturnElement * re = connection->returnElement; ++ if(strcmp(re->name,"volume")==0) { ++ status->volume = atoi(re->value); ++ } ++ else if(strcmp(re->name,"repeat")==0) { ++ status->repeat = atoi(re->value); ++ } ++ else if(strcmp(re->name,"random")==0) { ++ status->random = atoi(re->value); ++ } ++ else if(strcmp(re->name,"playlist")==0) { ++ status->playlist = strtol(re->value,NULL,10); ++ } ++ else if(strcmp(re->name,"playlistlength")==0) { ++ status->playlistLength = atoi(re->value); ++ } ++ else if(strcmp(re->name,"bitrate")==0) { ++ status->bitRate = atoi(re->value); ++ } ++ else if(strcmp(re->name,"state")==0) { ++ if(strcmp(re->value,"play")==0) { ++ status->state = MPD_STATUS_STATE_PLAY; ++ } ++ else if(strcmp(re->value,"stop")==0) { ++ status->state = MPD_STATUS_STATE_STOP; ++ } ++ else if(strcmp(re->value,"pause")==0) { ++ status->state = MPD_STATUS_STATE_PAUSE; ++ } ++ else { ++ status->state = MPD_STATUS_STATE_UNKNOWN; ++ } ++ } ++ else if(strcmp(re->name,"song")==0) { ++ status->song = atoi(re->value); ++ } ++ else if(strcmp(re->name,"songid")==0) { ++ status->songid = atoi(re->value); ++ } ++ else if(strcmp(re->name,"time")==0) { ++ char * tok = strchr(re->value,':'); ++ /* the second strchr below is a safety check */ ++ if (tok && (strchr(tok,0) > (tok+1))) { ++ /* atoi stops at the first non-[0-9] char: */ ++ status->elapsedTime = atoi(re->value); ++ status->totalTime = atoi(tok+1); ++ } ++ } ++ else if(strcmp(re->name,"error")==0) { ++ status->error = strdup(re->value); ++ } ++ else if(strcmp(re->name,"xfade")==0) { ++ status->crossfade = atoi(re->value); ++ } ++ else if(strcmp(re->name,"updating_db")==0) { ++ status->updatingDb = atoi(re->value); ++ } ++ else if(strcmp(re->name,"audio")==0) { ++ char * tok = strchr(re->value,':'); ++ if (tok && (strchr(tok,0) > (tok+1))) { ++ status->sampleRate = atoi(re->value); ++ status->bits = atoi(++tok); ++ tok = strchr(tok,':'); ++ if (tok && (strchr(tok,0) > (tok+1))) ++ status->channels = atoi(tok+1); ++ } ++ } ++ ++ mpd_getNextReturnElement(connection); ++ if(connection->error) { ++ free(status); ++ return NULL; ++ } ++ } ++ ++ if(connection->error) { ++ free(status); ++ return NULL; ++ } ++ else if(status->state<0) { ++ strcpy(connection->errorStr,"state not found"); ++ connection->error = 1; ++ free(status); ++ return NULL; ++ } ++ ++ return status; ++} ++ ++void mpd_freeStatus(mpd_Status * status) { ++ if(status->error) free(status->error); ++ free(status); ++} ++ ++void mpd_sendStatsCommand(mpd_Connection * connection) { ++ mpd_executeCommand(connection,"stats\n"); ++} ++ ++mpd_Stats * mpd_getStats(mpd_Connection * connection) { ++ mpd_Stats * stats; ++ ++ /*mpd_executeCommand(connection,"stats\n"); ++ ++ if(connection->error) return NULL;*/ ++ ++ if(connection->doneProcessing || (connection->listOks && ++ connection->doneListOk)) ++ { ++ return NULL; ++ } ++ ++ if(!connection->returnElement) mpd_getNextReturnElement(connection); ++ ++ stats = (mpd_Stats *)malloc(sizeof(mpd_Stats)); ++ stats->numberOfArtists = 0; ++ stats->numberOfAlbums = 0; ++ stats->numberOfSongs = 0; ++ stats->uptime = 0; ++ stats->dbUpdateTime = 0; ++ stats->playTime = 0; ++ stats->dbPlayTime = 0; ++ ++ if(connection->error) { ++ free(stats); ++ return NULL; ++ } ++ while(connection->returnElement) { ++ mpd_ReturnElement * re = connection->returnElement; ++ if(strcmp(re->name,"artists")==0) { ++ stats->numberOfArtists = atoi(re->value); ++ } ++ else if(strcmp(re->name,"albums")==0) { ++ stats->numberOfAlbums = atoi(re->value); ++ } ++ else if(strcmp(re->name,"songs")==0) { ++ stats->numberOfSongs = atoi(re->value); ++ } ++ else if(strcmp(re->name,"uptime")==0) { ++ stats->uptime = strtol(re->value,NULL,10); ++ } ++ else if(strcmp(re->name,"db_update")==0) { ++ stats->dbUpdateTime = strtol(re->value,NULL,10); ++ } ++ else if(strcmp(re->name,"playtime")==0) { ++ stats->playTime = strtol(re->value,NULL,10); ++ } ++ else if(strcmp(re->name,"db_playtime")==0) { ++ stats->dbPlayTime = strtol(re->value,NULL,10); ++ } ++ ++ mpd_getNextReturnElement(connection); ++ if(connection->error) { ++ free(stats); ++ return NULL; ++ } ++ } ++ ++ if(connection->error) { ++ free(stats); ++ return NULL; ++ } ++ ++ return stats; ++} ++ ++void mpd_freeStats(mpd_Stats * stats) { ++ free(stats); ++} ++ ++mpd_SearchStats * mpd_getSearchStats(mpd_Connection * connection) ++{ ++ mpd_SearchStats * stats; ++ mpd_ReturnElement * re; ++ ++ if (connection->doneProcessing || ++ (connection->listOks && connection->doneListOk)) { ++ return NULL; ++ } ++ ++ if (!connection->returnElement) mpd_getNextReturnElement(connection); ++ ++ if (connection->error) ++ return NULL; ++ ++ stats = (mpd_SearchStats *)malloc(sizeof(mpd_SearchStats)); ++ stats->numberOfSongs = 0; ++ stats->playTime = 0; ++ ++ while (connection->returnElement) { ++ re = connection->returnElement; ++ ++ if (strcmp(re->name, "songs") == 0) { ++ stats->numberOfSongs = atoi(re->value); ++ } else if (strcmp(re->name, "playtime") == 0) { ++ stats->playTime = strtol(re->value, NULL, 10); ++ } ++ ++ mpd_getNextReturnElement(connection); ++ if (connection->error) { ++ free(stats); ++ return NULL; ++ } ++ } ++ ++ if (connection->error) { ++ free(stats); ++ return NULL; ++ } ++ ++ return stats; ++} ++ ++void mpd_freeSearchStats(mpd_SearchStats * stats) ++{ ++ free(stats); ++} ++ ++static void mpd_initSong(mpd_Song * song) { ++ song->file = NULL; ++ song->artist = NULL; ++ song->album = NULL; ++ song->track = NULL; ++ song->title = NULL; ++ song->name = NULL; ++ song->date = NULL; ++ /* added by Qball */ ++ song->genre = NULL; ++ song->composer = NULL; ++ song->performer = NULL; ++ song->disc = NULL; ++ song->comment = NULL; ++ ++ song->time = MPD_SONG_NO_TIME; ++ song->pos = MPD_SONG_NO_NUM; ++ song->id = MPD_SONG_NO_ID; ++} ++ ++static void mpd_finishSong(mpd_Song * song) { ++ if(song->file) free(song->file); ++ if(song->artist) free(song->artist); ++ if(song->album) free(song->album); ++ if(song->title) free(song->title); ++ if(song->track) free(song->track); ++ if(song->name) free(song->name); ++ if(song->date) free(song->date); ++ if(song->genre) free(song->genre); ++ if(song->composer) free(song->composer); ++ if(song->disc) free(song->disc); ++ if(song->comment) free(song->comment); ++} ++ ++mpd_Song * mpd_newSong(void) { ++ mpd_Song * ret = (mpd_Song *)malloc(sizeof(mpd_Song)); ++ ++ mpd_initSong(ret); ++ ++ return ret; ++} ++ ++void mpd_freeSong(mpd_Song * song) { ++ mpd_finishSong(song); ++ free(song); ++} ++ ++mpd_Song * mpd_songDup(mpd_Song * song) { ++ mpd_Song * ret = mpd_newSong(); ++ ++ if(song->file) ret->file = strdup(song->file); ++ if(song->artist) ret->artist = strdup(song->artist); ++ if(song->album) ret->album = strdup(song->album); ++ if(song->title) ret->title = strdup(song->title); ++ if(song->track) ret->track = strdup(song->track); ++ if(song->name) ret->name = strdup(song->name); ++ if(song->date) ret->date = strdup(song->date); ++ if(song->genre) ret->genre= strdup(song->genre); ++ if(song->composer) ret->composer= strdup(song->composer); ++ if(song->disc) ret->disc = strdup(song->disc); ++ if(song->comment) ret->comment = strdup(song->comment); ++ ret->time = song->time; ++ ret->pos = song->pos; ++ ret->id = song->id; ++ ++ return ret; ++} ++ ++static void mpd_initDirectory(mpd_Directory * directory) { ++ directory->path = NULL; ++} ++ ++static void mpd_finishDirectory(mpd_Directory * directory) { ++ if(directory->path) free(directory->path); ++} ++ ++mpd_Directory * mpd_newDirectory(void) { ++ mpd_Directory * directory = (mpd_Directory *)malloc(sizeof(mpd_Directory));; ++ ++ mpd_initDirectory(directory); ++ ++ return directory; ++} ++ ++void mpd_freeDirectory(mpd_Directory * directory) { ++ mpd_finishDirectory(directory); ++ ++ free(directory); ++} ++ ++mpd_Directory * mpd_directoryDup(mpd_Directory * directory) { ++ mpd_Directory * ret = mpd_newDirectory(); ++ ++ if(directory->path) ret->path = strdup(directory->path); ++ ++ return ret; ++} ++ ++static void mpd_initPlaylistFile(mpd_PlaylistFile * playlist) { ++ playlist->path = NULL; ++} ++ ++static void mpd_finishPlaylistFile(mpd_PlaylistFile * playlist) { ++ if(playlist->path) free(playlist->path); ++} ++ ++mpd_PlaylistFile * mpd_newPlaylistFile(void) { ++ mpd_PlaylistFile * playlist = (mpd_PlaylistFile *)malloc(sizeof(mpd_PlaylistFile)); ++ ++ mpd_initPlaylistFile(playlist); ++ ++ return playlist; ++} ++ ++void mpd_freePlaylistFile(mpd_PlaylistFile * playlist) { ++ mpd_finishPlaylistFile(playlist); ++ free(playlist); ++} ++ ++mpd_PlaylistFile * mpd_playlistFileDup(mpd_PlaylistFile * playlist) { ++ mpd_PlaylistFile * ret = mpd_newPlaylistFile(); ++ ++ if(playlist->path) ret->path = strdup(playlist->path); ++ ++ return ret; ++} ++ ++static void mpd_initInfoEntity(mpd_InfoEntity * entity) { ++ entity->info.directory = NULL; ++} ++ ++static void mpd_finishInfoEntity(mpd_InfoEntity * entity) { ++ if(entity->info.directory) { ++ if(entity->type == MPD_INFO_ENTITY_TYPE_DIRECTORY) { ++ mpd_freeDirectory(entity->info.directory); ++ } ++ else if(entity->type == MPD_INFO_ENTITY_TYPE_SONG) { ++ mpd_freeSong(entity->info.song); ++ } ++ else if(entity->type == MPD_INFO_ENTITY_TYPE_PLAYLISTFILE) { ++ mpd_freePlaylistFile(entity->info.playlistFile); ++ } ++ } ++} ++ ++mpd_InfoEntity * mpd_newInfoEntity(void) { ++ mpd_InfoEntity * entity = (mpd_InfoEntity *)malloc(sizeof(mpd_InfoEntity)); ++ ++ mpd_initInfoEntity(entity); ++ ++ return entity; ++} ++ ++void mpd_freeInfoEntity(mpd_InfoEntity * entity) { ++ mpd_finishInfoEntity(entity); ++ free(entity); ++} ++ ++static void mpd_sendInfoCommand(mpd_Connection * connection, char * command) { ++ mpd_executeCommand(connection,command); ++} ++ ++mpd_InfoEntity * mpd_getNextInfoEntity(mpd_Connection * connection) { ++ mpd_InfoEntity * entity = NULL; ++ ++ if(connection->doneProcessing || (connection->listOks && ++ connection->doneListOk)) ++ { ++ return NULL; ++ } ++ ++ if(!connection->returnElement) mpd_getNextReturnElement(connection); ++ ++ if(connection->returnElement) { ++ if(strcmp(connection->returnElement->name,"file")==0) { ++ entity = mpd_newInfoEntity(); ++ entity->type = MPD_INFO_ENTITY_TYPE_SONG; ++ entity->info.song = mpd_newSong(); ++ entity->info.song->file = ++ strdup(connection->returnElement->value); ++ } ++ else if(strcmp(connection->returnElement->name, ++ "directory")==0) { ++ entity = mpd_newInfoEntity(); ++ entity->type = MPD_INFO_ENTITY_TYPE_DIRECTORY; ++ entity->info.directory = mpd_newDirectory(); ++ entity->info.directory->path = ++ strdup(connection->returnElement->value); ++ } ++ else if(strcmp(connection->returnElement->name,"playlist")==0) { ++ entity = mpd_newInfoEntity(); ++ entity->type = MPD_INFO_ENTITY_TYPE_PLAYLISTFILE; ++ entity->info.playlistFile = mpd_newPlaylistFile(); ++ entity->info.playlistFile->path = ++ strdup(connection->returnElement->value); ++ } ++ else if(strcmp(connection->returnElement->name, "cpos") == 0){ ++ entity = mpd_newInfoEntity(); ++ entity->type = MPD_INFO_ENTITY_TYPE_SONG; ++ entity->info.song = mpd_newSong(); ++ entity->info.song->pos = atoi(connection->returnElement->value); ++ } ++ else { ++ connection->error = 1; ++ strcpy(connection->errorStr,"problem parsing song info"); ++ return NULL; ++ } ++ } ++ else return NULL; ++ ++ mpd_getNextReturnElement(connection); ++ while(connection->returnElement) { ++ mpd_ReturnElement * re = connection->returnElement; ++ ++ if(strcmp(re->name,"file")==0) return entity; ++ else if(strcmp(re->name,"directory")==0) return entity; ++ else if(strcmp(re->name,"playlist")==0) return entity; ++ else if(strcmp(re->name,"cpos")==0) return entity; ++ ++ if(entity->type == MPD_INFO_ENTITY_TYPE_SONG && ++ strlen(re->value)) { ++ if(!entity->info.song->artist && ++ strcmp(re->name,"Artist")==0) { ++ entity->info.song->artist = strdup(re->value); ++ } ++ else if(!entity->info.song->album && ++ strcmp(re->name,"Album")==0) { ++ entity->info.song->album = strdup(re->value); ++ } ++ else if(!entity->info.song->title && ++ strcmp(re->name,"Title")==0) { ++ entity->info.song->title = strdup(re->value); ++ } ++ else if(!entity->info.song->track && ++ strcmp(re->name,"Track")==0) { ++ entity->info.song->track = strdup(re->value); ++ } ++ else if(!entity->info.song->name && ++ strcmp(re->name,"Name")==0) { ++ entity->info.song->name = strdup(re->value); ++ } ++ else if(entity->info.song->time==MPD_SONG_NO_TIME && ++ strcmp(re->name,"Time")==0) { ++ entity->info.song->time = atoi(re->value); ++ } ++ else if(entity->info.song->pos==MPD_SONG_NO_NUM && ++ strcmp(re->name,"Pos")==0) { ++ entity->info.song->pos = atoi(re->value); ++ } ++ else if(entity->info.song->id==MPD_SONG_NO_ID && ++ strcmp(re->name,"Id")==0) { ++ entity->info.song->id = atoi(re->value); ++ } ++ else if(!entity->info.song->date && ++ strcmp(re->name, "Date") == 0) { ++ entity->info.song->date = strdup(re->value); ++ } ++ else if(!entity->info.song->genre && ++ strcmp(re->name, "Genre") == 0) { ++ entity->info.song->genre = strdup(re->value); ++ } ++ else if(!entity->info.song->composer && ++ strcmp(re->name, "Composer") == 0) { ++ entity->info.song->composer = strdup(re->value); ++ } ++ else if(!entity->info.song->performer && ++ strcmp(re->name, "Performer") == 0) { ++ entity->info.song->performer = strdup(re->value); ++ } ++ else if(!entity->info.song->disc && ++ strcmp(re->name, "Disc") == 0) { ++ entity->info.song->disc = strdup(re->value); ++ } ++ else if(!entity->info.song->comment && ++ strcmp(re->name, "Comment") == 0) { ++ entity->info.song->comment = strdup(re->value); ++ } ++ } ++ else if(entity->type == MPD_INFO_ENTITY_TYPE_DIRECTORY) { ++ } ++ else if(entity->type == MPD_INFO_ENTITY_TYPE_PLAYLISTFILE) { ++ } ++ ++ mpd_getNextReturnElement(connection); ++ } ++ ++ return entity; ++} ++ ++static char * mpd_getNextReturnElementNamed(mpd_Connection * connection, ++ const char * name) ++{ ++ if(connection->doneProcessing || (connection->listOks && ++ connection->doneListOk)) ++ { ++ return NULL; ++ } ++ ++ mpd_getNextReturnElement(connection); ++ while(connection->returnElement) { ++ mpd_ReturnElement * re = connection->returnElement; ++ ++ if(strcmp(re->name,name)==0) return strdup(re->value); ++ mpd_getNextReturnElement(connection); ++ } ++ ++ return NULL; ++} ++ ++char *mpd_getNextTag(mpd_Connection *connection, int type) ++{ ++ if (type < 0 || type >= MPD_TAG_NUM_OF_ITEM_TYPES || ++ type == MPD_TAG_ITEM_ANY) ++ return NULL; ++ if (type == MPD_TAG_ITEM_FILENAME) ++ return mpd_getNextReturnElementNamed(connection, "file"); ++ return mpd_getNextReturnElementNamed(connection, mpdTagItemKeys[type]); ++} ++ ++char * mpd_getNextArtist(mpd_Connection * connection) { ++ return mpd_getNextReturnElementNamed(connection,"Artist"); ++} ++ ++char * mpd_getNextAlbum(mpd_Connection * connection) { ++ return mpd_getNextReturnElementNamed(connection,"Album"); ++} ++ ++void mpd_sendPlaylistInfoCommand(mpd_Connection * connection, int songPos) { ++ int len = strlen("playlistinfo")+2+INTLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "playlistinfo \"%i\"\n", songPos); ++ mpd_sendInfoCommand(connection,string); ++ free(string); ++} ++ ++void mpd_sendPlaylistIdCommand(mpd_Connection * connection, int id) { ++ int len = strlen("playlistid")+2+INTLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "playlistid \"%i\"\n", id); ++ mpd_sendInfoCommand(connection, string); ++ free(string); ++} ++ ++void mpd_sendPlChangesCommand(mpd_Connection * connection, long long playlist) { ++ int len = strlen("plchanges")+2+LONGLONGLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "plchanges \"%lld\"\n", playlist); ++ mpd_sendInfoCommand(connection,string); ++ free(string); ++} ++ ++void mpd_sendPlChangesPosIdCommand(mpd_Connection * connection, long long playlist) { ++ int len = strlen("plchangesposid")+2+LONGLONGLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "plchangesposid \"%lld\"\n", playlist); ++ mpd_sendInfoCommand(connection,string); ++ free(string); ++} ++ ++void mpd_sendListallCommand(mpd_Connection * connection, const char * dir) { ++ char * sDir = mpd_sanitizeArg(dir); ++ int len = strlen("listall")+2+strlen(sDir)+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "listall \"%s\"\n", sDir); ++ mpd_sendInfoCommand(connection,string); ++ free(string); ++ free(sDir); ++} ++ ++void mpd_sendListallInfoCommand(mpd_Connection * connection, const char * dir) { ++ char * sDir = mpd_sanitizeArg(dir); ++ int len = strlen("listallinfo")+2+strlen(sDir)+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "listallinfo \"%s\"\n", sDir); ++ mpd_sendInfoCommand(connection,string); ++ free(string); ++ free(sDir); ++} ++ ++void mpd_sendLsInfoCommand(mpd_Connection * connection, const char * dir) { ++ char * sDir = mpd_sanitizeArg(dir); ++ int len = strlen("lsinfo")+2+strlen(sDir)+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "lsinfo \"%s\"\n", sDir); ++ mpd_sendInfoCommand(connection,string); ++ free(string); ++ free(sDir); ++} ++ ++void mpd_sendCurrentSongCommand(mpd_Connection * connection) { ++ mpd_executeCommand(connection,"currentsong\n"); ++} ++ ++void mpd_sendSearchCommand(mpd_Connection * connection, int table, ++ const char * str) ++{ ++ mpd_startSearch(connection, 0); ++ mpd_addConstraintSearch(connection, table, str); ++ mpd_commitSearch(connection); ++} ++ ++void mpd_sendFindCommand(mpd_Connection * connection, int table, ++ const char * str) ++{ ++ mpd_startSearch(connection, 1); ++ mpd_addConstraintSearch(connection, table, str); ++ mpd_commitSearch(connection); ++} ++ ++void mpd_sendListCommand(mpd_Connection * connection, int table, ++ const char * arg1) ++{ ++ char st[10]; ++ int len; ++ char *string; ++ if(table == MPD_TABLE_ARTIST) strcpy(st,"artist"); ++ else if(table == MPD_TABLE_ALBUM) strcpy(st,"album"); ++ else { ++ connection->error = 1; ++ strcpy(connection->errorStr,"unknown table for list"); ++ return; ++ } ++ if(arg1) { ++ char * sanitArg1 = mpd_sanitizeArg(arg1); ++ len = strlen("list")+1+strlen(sanitArg1)+2+strlen(st)+3; ++ string = (char *)malloc(len); ++ snprintf(string, len, "list %s \"%s\"\n", st, sanitArg1); ++ free(sanitArg1); ++ } ++ else { ++ len = strlen("list")+1+strlen(st)+2; ++ string = (char *)malloc(len); ++ snprintf(string, len, "list %s\n", st); ++ } ++ mpd_sendInfoCommand(connection,string); ++ free(string); ++} ++ ++void mpd_sendAddCommand(mpd_Connection * connection, const char * file) { ++ char * sFile = mpd_sanitizeArg(file); ++ int len = strlen("add")+2+strlen(sFile)+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "add \"%s\"\n", sFile); ++ mpd_executeCommand(connection,string); ++ free(string); ++ free(sFile); ++} ++ ++int mpd_sendAddIdCommand(mpd_Connection *connection, const char *file) ++{ ++ int retval = -1; ++ char *sFile = mpd_sanitizeArg(file); ++ int len = strlen("addid")+2+strlen(sFile)+3; ++ char *string = (char *)malloc(len); ++ ++ snprintf(string, len, "addid \"%s\"\n", sFile); ++ mpd_sendInfoCommand(connection, string); ++ free(string); ++ free(sFile); ++ ++ string = mpd_getNextReturnElementNamed(connection, "Id"); ++ if (string) { ++ retval = atoi(string); ++ free(string); ++ } ++ ++ return retval; ++} ++ ++void mpd_sendDeleteCommand(mpd_Connection * connection, int songPos) { ++ int len = strlen("delete")+2+INTLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "delete \"%i\"\n", songPos); ++ mpd_sendInfoCommand(connection,string); ++ free(string); ++} ++ ++void mpd_sendDeleteIdCommand(mpd_Connection * connection, int id) { ++ int len = strlen("deleteid")+2+INTLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "deleteid \"%i\"\n", id); ++ mpd_sendInfoCommand(connection,string); ++ free(string); ++} ++ ++void mpd_sendSaveCommand(mpd_Connection * connection, const char * name) { ++ char * sName = mpd_sanitizeArg(name); ++ int len = strlen("save")+2+strlen(sName)+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "save \"%s\"\n", sName); ++ mpd_executeCommand(connection,string); ++ free(string); ++ free(sName); ++} ++ ++void mpd_sendLoadCommand(mpd_Connection * connection, const char * name) { ++ char * sName = mpd_sanitizeArg(name); ++ int len = strlen("load")+2+strlen(sName)+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "load \"%s\"\n", sName); ++ mpd_executeCommand(connection,string); ++ free(string); ++ free(sName); ++} ++ ++void mpd_sendRmCommand(mpd_Connection * connection, const char * name) { ++ char * sName = mpd_sanitizeArg(name); ++ int len = strlen("rm")+2+strlen(sName)+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "rm \"%s\"\n", sName); ++ mpd_executeCommand(connection,string); ++ free(string); ++ free(sName); ++} ++ ++void mpd_sendRenameCommand(mpd_Connection *connection, const char *from, ++ const char *to) ++{ ++ char *sFrom = mpd_sanitizeArg(from); ++ char *sTo = mpd_sanitizeArg(to); ++ int len = strlen("rename")+2+strlen(sFrom)+3+strlen(sTo)+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "rename \"%s\" \"%s\"\n", sFrom, sTo); ++ mpd_executeCommand(connection, string); ++ free(string); ++ free(sFrom); ++ free(sTo); ++} ++ ++void mpd_sendShuffleCommand(mpd_Connection * connection) { ++ mpd_executeCommand(connection,"shuffle\n"); ++} ++ ++void mpd_sendClearCommand(mpd_Connection * connection) { ++ mpd_executeCommand(connection,"clear\n"); ++} ++ ++void mpd_sendPlayCommand(mpd_Connection * connection, int songPos) { ++ int len = strlen("play")+2+INTLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "play \"%i\"\n", songPos); ++ mpd_sendInfoCommand(connection,string); ++ free(string); ++} ++ ++void mpd_sendPlayIdCommand(mpd_Connection * connection, int id) { ++ int len = strlen("playid")+2+INTLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "playid \"%i\"\n", id); ++ mpd_sendInfoCommand(connection,string); ++ free(string); ++} ++ ++void mpd_sendStopCommand(mpd_Connection * connection) { ++ mpd_executeCommand(connection,"stop\n"); ++} ++ ++void mpd_sendPauseCommand(mpd_Connection * connection, int pauseMode) { ++ int len = strlen("pause")+2+INTLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "pause \"%i\"\n", pauseMode); ++ mpd_executeCommand(connection,string); ++ free(string); ++} ++ ++void mpd_sendNextCommand(mpd_Connection * connection) { ++ mpd_executeCommand(connection,"next\n"); ++} ++ ++void mpd_sendMoveCommand(mpd_Connection * connection, int from, int to) { ++ int len = strlen("move")+2+INTLEN+3+INTLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "move \"%i\" \"%i\"\n", from, to); ++ mpd_sendInfoCommand(connection,string); ++ free(string); ++} ++ ++void mpd_sendMoveIdCommand(mpd_Connection * connection, int id, int to) { ++ int len = strlen("moveid")+2+INTLEN+3+INTLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "moveid \"%i\" \"%i\"\n", id, to); ++ mpd_sendInfoCommand(connection,string); ++ free(string); ++} ++ ++void mpd_sendSwapCommand(mpd_Connection * connection, int song1, int song2) { ++ int len = strlen("swap")+2+INTLEN+3+INTLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "swap \"%i\" \"%i\"\n", song1, song2); ++ mpd_sendInfoCommand(connection,string); ++ free(string); ++} ++ ++void mpd_sendSwapIdCommand(mpd_Connection * connection, int id1, int id2) { ++ int len = strlen("swapid")+2+INTLEN+3+INTLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "swapid \"%i\" \"%i\"\n", id1, id2); ++ mpd_sendInfoCommand(connection,string); ++ free(string); ++} ++ ++void mpd_sendSeekCommand(mpd_Connection * connection, int song, int time) { ++ int len = strlen("seek")+2+INTLEN+3+INTLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "seek \"%i\" \"%i\"\n", song, time); ++ mpd_sendInfoCommand(connection,string); ++ free(string); ++} ++ ++void mpd_sendSeekIdCommand(mpd_Connection * connection, int id, int time) { ++ int len = strlen("seekid")+2+INTLEN+3+INTLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "seekid \"%i\" \"%i\"\n", id, time); ++ mpd_sendInfoCommand(connection,string); ++ free(string); ++} ++ ++void mpd_sendUpdateCommand(mpd_Connection * connection, char * path) { ++ char * sPath = mpd_sanitizeArg(path); ++ int len = strlen("update")+2+strlen(sPath)+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "update \"%s\"\n", sPath); ++ mpd_sendInfoCommand(connection,string); ++ free(string); ++ free(sPath); ++} ++ ++int mpd_getUpdateId(mpd_Connection * connection) { ++ char * jobid; ++ int ret = 0; ++ ++ jobid = mpd_getNextReturnElementNamed(connection,"updating_db"); ++ if(jobid) { ++ ret = atoi(jobid); ++ free(jobid); ++ } ++ ++ return ret; ++} ++ ++void mpd_sendPrevCommand(mpd_Connection * connection) { ++ mpd_executeCommand(connection,"previous\n"); ++} ++ ++void mpd_sendRepeatCommand(mpd_Connection * connection, int repeatMode) { ++ int len = strlen("repeat")+2+INTLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "repeat \"%i\"\n", repeatMode); ++ mpd_executeCommand(connection,string); ++ free(string); ++} ++ ++void mpd_sendRandomCommand(mpd_Connection * connection, int randomMode) { ++ int len = strlen("random")+2+INTLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "random \"%i\"\n", randomMode); ++ mpd_executeCommand(connection,string); ++ free(string); ++} ++ ++void mpd_sendSetvolCommand(mpd_Connection * connection, int volumeChange) { ++ int len = strlen("setvol")+2+INTLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "setvol \"%i\"\n", volumeChange); ++ mpd_executeCommand(connection,string); ++ free(string); ++} ++ ++void mpd_sendVolumeCommand(mpd_Connection * connection, int volumeChange) { ++ int len = strlen("volume")+2+INTLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "volume \"%i\"\n", volumeChange); ++ mpd_executeCommand(connection,string); ++ free(string); ++} ++ ++void mpd_sendCrossfadeCommand(mpd_Connection * connection, int seconds) { ++ int len = strlen("crossfade")+2+INTLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "crossfade \"%i\"\n", seconds); ++ mpd_executeCommand(connection,string); ++ free(string); ++} ++ ++void mpd_sendPasswordCommand(mpd_Connection * connection, const char * pass) { ++ char * sPass = mpd_sanitizeArg(pass); ++ int len = strlen("password")+2+strlen(sPass)+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "password \"%s\"\n", sPass); ++ mpd_executeCommand(connection,string); ++ free(string); ++ free(sPass); ++} ++ ++void mpd_sendCommandListBegin(mpd_Connection * connection) { ++ if(connection->commandList) { ++ strcpy(connection->errorStr,"already in command list mode"); ++ connection->error = 1; ++ return; ++ } ++ connection->commandList = COMMAND_LIST; ++ mpd_executeCommand(connection,"command_list_begin\n"); ++} ++ ++void mpd_sendCommandListOkBegin(mpd_Connection * connection) { ++ if(connection->commandList) { ++ strcpy(connection->errorStr,"already in command list mode"); ++ connection->error = 1; ++ return; ++ } ++ connection->commandList = COMMAND_LIST_OK; ++ mpd_executeCommand(connection,"command_list_ok_begin\n"); ++ connection->listOks = 0; ++} ++ ++void mpd_sendCommandListEnd(mpd_Connection * connection) { ++ if(!connection->commandList) { ++ strcpy(connection->errorStr,"not in command list mode"); ++ connection->error = 1; ++ return; ++ } ++ connection->commandList = 0; ++ mpd_executeCommand(connection,"command_list_end\n"); ++} ++ ++void mpd_sendOutputsCommand(mpd_Connection * connection) { ++ mpd_executeCommand(connection,"outputs\n"); ++} ++ ++mpd_OutputEntity * mpd_getNextOutput(mpd_Connection * connection) { ++ mpd_OutputEntity * output = NULL; ++ ++ if(connection->doneProcessing || (connection->listOks && ++ connection->doneListOk)) ++ { ++ return NULL; ++ } ++ ++ if(connection->error) return NULL; ++ ++ output = (mpd_OutputEntity *)malloc(sizeof(mpd_OutputEntity)); ++ output->id = -10; ++ output->name = NULL; ++ output->enabled = 0; ++ ++ if(!connection->returnElement) mpd_getNextReturnElement(connection); ++ ++ while(connection->returnElement) { ++ mpd_ReturnElement * re = connection->returnElement; ++ if(strcmp(re->name,"outputid")==0) { ++ if(output!=NULL && output->id>=0) return output; ++ output->id = atoi(re->value); ++ } ++ else if(strcmp(re->name,"outputname")==0) { ++ output->name = strdup(re->value); ++ } ++ else if(strcmp(re->name,"outputenabled")==0) { ++ output->enabled = atoi(re->value); ++ } ++ ++ mpd_getNextReturnElement(connection); ++ if(connection->error) { ++ free(output); ++ return NULL; ++ } ++ ++ } ++ ++ return output; ++} ++ ++void mpd_sendEnableOutputCommand(mpd_Connection * connection, int outputId) { ++ int len = strlen("enableoutput")+2+INTLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "enableoutput \"%i\"\n", outputId); ++ mpd_executeCommand(connection,string); ++ free(string); ++} ++ ++void mpd_sendDisableOutputCommand(mpd_Connection * connection, int outputId) { ++ int len = strlen("disableoutput")+2+INTLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "disableoutput \"%i\"\n", outputId); ++ mpd_executeCommand(connection,string); ++ free(string); ++} ++ ++void mpd_freeOutputElement(mpd_OutputEntity * output) { ++ free(output->name); ++ free(output); ++} ++ ++/** ++ * mpd_sendNotCommandsCommand ++ * odd naming, but it gets the not allowed commands ++ */ ++ ++void mpd_sendNotCommandsCommand(mpd_Connection * connection) ++{ ++ mpd_executeCommand(connection, "notcommands\n"); ++} ++ ++/** ++ * mpd_sendCommandsCommand ++ * odd naming, but it gets the allowed commands ++ */ ++void mpd_sendCommandsCommand(mpd_Connection * connection) ++{ ++ mpd_executeCommand(connection, "commands\n"); ++} ++ ++/** ++ * Get the next returned command ++ */ ++char * mpd_getNextCommand(mpd_Connection * connection) ++{ ++ return mpd_getNextReturnElementNamed(connection, "command"); ++} ++ ++void mpd_sendUrlHandlersCommand(mpd_Connection * connection) ++{ ++ mpd_executeCommand(connection, "urlhandlers\n"); ++} ++ ++char * mpd_getNextHandler(mpd_Connection * connection) ++{ ++ return mpd_getNextReturnElementNamed(connection, "handler"); ++} ++ ++void mpd_sendTagTypesCommand(mpd_Connection * connection) ++{ ++ mpd_executeCommand(connection, "tagtypes\n"); ++} ++ ++char * mpd_getNextTagType(mpd_Connection * connection) ++{ ++ return mpd_getNextReturnElementNamed(connection, "tagtype"); ++} ++ ++void mpd_startSearch(mpd_Connection *connection, int exact) ++{ ++ if (connection->request) { ++ strcpy(connection->errorStr, "search already in progress"); ++ connection->error = 1; ++ return; ++ } ++ ++ if (exact) connection->request = strdup("find"); ++ else connection->request = strdup("search"); ++} ++ ++void mpd_startStatsSearch(mpd_Connection *connection) ++{ ++ if (connection->request) { ++ strcpy(connection->errorStr, "search already in progress"); ++ connection->error = 1; ++ return; ++ } ++ ++ connection->request = strdup("count"); ++} ++ ++void mpd_startPlaylistSearch(mpd_Connection *connection, int exact) ++{ ++ if (connection->request) { ++ strcpy(connection->errorStr, "search already in progress"); ++ connection->error = 1; ++ return; ++ } ++ ++ if (exact) connection->request = strdup("playlistfind"); ++ else connection->request = strdup("playlistsearch"); ++} ++ ++void mpd_startFieldSearch(mpd_Connection *connection, int type) ++{ ++ char *strtype; ++ int len; ++ ++ if (connection->request) { ++ strcpy(connection->errorStr, "search already in progress"); ++ connection->error = 1; ++ return; ++ } ++ ++ if (type < 0 || type >= MPD_TAG_NUM_OF_ITEM_TYPES) { ++ strcpy(connection->errorStr, "invalid type specified"); ++ connection->error = 1; ++ return; ++ } ++ ++ strtype = mpdTagItemKeys[type]; ++ ++ len = 5+strlen(strtype)+1; ++ connection->request = (char *)malloc(len); ++ ++ snprintf(connection->request, len, "list %c%s", ++ tolower(strtype[0]), strtype+1); ++} ++ ++void mpd_addConstraintSearch(mpd_Connection *connection, int type, const char *name) ++{ ++ char *strtype; ++ char *arg; ++ int len; ++ char *string; ++ ++ if (!connection->request) { ++ strcpy(connection->errorStr, "no search in progress"); ++ connection->error = 1; ++ return; ++ } ++ ++ if (type < 0 || type >= MPD_TAG_NUM_OF_ITEM_TYPES) { ++ strcpy(connection->errorStr, "invalid type specified"); ++ connection->error = 1; ++ return; ++ } ++ ++ if (name == NULL) { ++ strcpy(connection->errorStr, "no name specified"); ++ connection->error = 1; ++ return; ++ } ++ ++ string = strdup(connection->request); ++ strtype = mpdTagItemKeys[type]; ++ arg = mpd_sanitizeArg(name); ++ ++ len = strlen(string)+1+strlen(strtype)+2+strlen(arg)+2; ++ connection->request = (char *)realloc(connection->request, len); ++ snprintf(connection->request, len, "%s %c%s \"%s\"", ++ string, tolower(strtype[0]), strtype+1, arg); ++ ++ free(string); ++ free(arg); ++} ++ ++void mpd_commitSearch(mpd_Connection *connection) ++{ ++ int len; ++ ++ if (!connection->request) { ++ strcpy(connection->errorStr, "no search in progress"); ++ connection->error = 1; ++ return; ++ } ++ ++ len = strlen(connection->request)+2; ++ connection->request = (char *)realloc(connection->request, len); ++ connection->request[len-2] = '\n'; ++ connection->request[len-1] = '\0'; ++ mpd_sendInfoCommand(connection, connection->request); ++ ++ free(connection->request); ++ connection->request = NULL; ++} ++ ++/** ++ * @param connection a MpdConnection ++ * @param path the path to the playlist. ++ * ++ * List the content, with full metadata, of a stored playlist. ++ * ++ */ ++void mpd_sendListPlaylistInfoCommand(mpd_Connection *connection, char *path) ++{ ++ char *arg = mpd_sanitizeArg(path); ++ int len = strlen("listplaylistinfo")+2+strlen(arg)+3; ++ char *query = (char *)malloc(len); ++ snprintf(query, len, "listplaylistinfo \"%s\"\n", arg); ++ mpd_sendInfoCommand(connection, query); ++ free(arg); ++ free(query); ++} ++ ++/** ++ * @param connection a MpdConnection ++ * @param path the path to the playlist. ++ * ++ * List the content of a stored playlist. ++ * ++ */ ++void mpd_sendListPlaylistCommand(mpd_Connection *connection, char *path) ++{ ++ char *arg = mpd_sanitizeArg(path); ++ int len = strlen("listplaylist")+2+strlen(arg)+3; ++ char *query = (char *)malloc(len); ++ snprintf(query, len, "listplaylist \"%s\"\n", arg); ++ mpd_sendInfoCommand(connection, query); ++ free(arg); ++ free(query); ++} ++ ++void mpd_sendPlaylistClearCommand(mpd_Connection *connection, char *path) ++{ ++ char *sPath = mpd_sanitizeArg(path); ++ int len = strlen("playlistclear")+2+strlen(sPath)+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "playlistclear \"%s\"\n", sPath); ++ mpd_executeCommand(connection, string); ++ free(sPath); ++ free(string); ++} ++ ++void mpd_sendPlaylistAddCommand(mpd_Connection *connection, ++ char *playlist, char *path) ++{ ++ char *sPlaylist = mpd_sanitizeArg(playlist); ++ char *sPath = mpd_sanitizeArg(path); ++ int len = strlen("playlistadd")+2+strlen(sPlaylist)+3+strlen(sPath)+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "playlistadd \"%s\" \"%s\"\n", sPlaylist, sPath); ++ mpd_executeCommand(connection, string); ++ free(sPlaylist); ++ free(sPath); ++ free(string); ++} ++ ++void mpd_sendPlaylistMoveCommand(mpd_Connection *connection, ++ char *playlist, int from, int to) ++{ ++ char *sPlaylist = mpd_sanitizeArg(playlist); ++ int len = strlen("playlistmove")+ ++ 2+strlen(sPlaylist)+3+INTLEN+3+INTLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "playlistmove \"%s\" \"%i\" \"%i\"\n", ++ sPlaylist, from, to); ++ mpd_executeCommand(connection, string); ++ free(sPlaylist); ++ free(string); ++} ++ ++void mpd_sendPlaylistDeleteCommand(mpd_Connection *connection, ++ char *playlist, int pos) ++{ ++ char *sPlaylist = mpd_sanitizeArg(playlist); ++ int len = strlen("playlistdelete")+2+strlen(sPlaylist)+3+INTLEN+3; ++ char *string = (char *)malloc(len); ++ snprintf(string, len, "playlistdelete \"%s\" \"%i\"\n", sPlaylist, pos); ++ mpd_executeCommand(connection, string); ++ free(sPlaylist); ++ free(string); ++} +diff -r 171db9560cb5 clients/mpd/libmpdclient.h +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/clients/mpd/libmpdclient.h Mon May 19 00:21:51 2008 -0400 +@@ -0,0 +1,670 @@ ++/* libmpdclient ++ (c)2003-2006 by Warren Dukes (warren.dukes@gmail.com) ++ This project's homepage is: http://www.musicpd.org ++ ++ Redistribution and use in source and binary forms, with or without ++ modification, are permitted provided that the following conditions ++ are met: ++ ++ - Redistributions of source code must retain the above copyright ++ notice, this list of conditions and the following disclaimer. ++ ++ - Redistributions in binary form must reproduce the above copyright ++ notice, this list of conditions and the following disclaimer in the ++ documentation and/or other materials provided with the distribution. ++ ++ - Neither the name of the Music Player Daemon nor the names of its ++ contributors may be used to endorse or promote products derived from ++ this software without specific prior written permission. ++ ++ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ++ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ++ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ++ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR ++ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, ++ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, ++ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR ++ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF ++ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING ++ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ++ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++*/ ++ ++#ifndef LIBMPDCLIENT_H ++#define LIBMPDCLIENT_H ++ ++#ifdef WIN32 ++# define __W32API_USE_DLLIMPORT__ 1 ++#endif ++ ++#include ++#include ++#define MPD_BUFFER_MAX_LENGTH 50000 ++#define MPD_ERRORSTR_MAX_LENGTH 1000 ++#define MPD_WELCOME_MESSAGE "OK MPD " ++ ++#define MPD_ERROR_TIMEOUT 10 /* timeout trying to talk to mpd */ ++#define MPD_ERROR_SYSTEM 11 /* system error */ ++#define MPD_ERROR_UNKHOST 12 /* unknown host */ ++#define MPD_ERROR_CONNPORT 13 /* problems connecting to port on host */ ++#define MPD_ERROR_NOTMPD 14 /* mpd not running on port at host */ ++#define MPD_ERROR_NORESPONSE 15 /* no response on attempting to connect */ ++#define MPD_ERROR_SENDING 16 /* error sending command */ ++#define MPD_ERROR_CONNCLOSED 17 /* connection closed by mpd */ ++#define MPD_ERROR_ACK 18 /* ACK returned! */ ++#define MPD_ERROR_BUFFEROVERRUN 19 /* Buffer was overrun! */ ++ ++#define MPD_ACK_ERROR_UNK -1 ++#define MPD_ERROR_AT_UNK -1 ++ ++#define MPD_ACK_ERROR_NOT_LIST 1 ++#define MPD_ACK_ERROR_ARG 2 ++#define MPD_ACK_ERROR_PASSWORD 3 ++#define MPD_ACK_ERROR_PERMISSION 4 ++#define MPD_ACK_ERROR_UNKNOWN_CMD 5 ++ ++#define MPD_ACK_ERROR_NO_EXIST 50 ++#define MPD_ACK_ERROR_PLAYLIST_MAX 51 ++#define MPD_ACK_ERROR_SYSTEM 52 ++#define MPD_ACK_ERROR_PLAYLIST_LOAD 53 ++#define MPD_ACK_ERROR_UPDATE_ALREADY 54 ++#define MPD_ACK_ERROR_PLAYER_SYNC 55 ++#define MPD_ACK_ERROR_EXIST 56 ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++typedef enum mpd_TagItems ++{ ++ MPD_TAG_ITEM_ARTIST, ++ MPD_TAG_ITEM_ALBUM, ++ MPD_TAG_ITEM_TITLE, ++ MPD_TAG_ITEM_TRACK, ++ MPD_TAG_ITEM_NAME, ++ MPD_TAG_ITEM_GENRE, ++ MPD_TAG_ITEM_DATE, ++ MPD_TAG_ITEM_COMPOSER, ++ MPD_TAG_ITEM_PERFORMER, ++ MPD_TAG_ITEM_COMMENT, ++ MPD_TAG_ITEM_DISC, ++ MPD_TAG_ITEM_FILENAME, ++ MPD_TAG_ITEM_ANY, ++ MPD_TAG_NUM_OF_ITEM_TYPES ++} mpd_TagItems; ++ ++extern char * mpdTagItemKeys[MPD_TAG_NUM_OF_ITEM_TYPES]; ++ ++/* internal stuff don't touch this struct */ ++typedef struct _mpd_ReturnElement { ++ char * name; ++ char * value; ++} mpd_ReturnElement; ++ ++/* mpd_Connection ++ * holds info about connection to mpd ++ * use error, and errorStr to detect errors ++ */ ++typedef struct _mpd_Connection { ++ /* use this to check the version of mpd */ ++ int version[3]; ++ /* IMPORTANT, you want to get the error messages from here */ ++ char errorStr[MPD_ERRORSTR_MAX_LENGTH+1]; ++ int errorCode; ++ int errorAt; ++ /* this will be set to MPD_ERROR_* if there is an error, 0 if not */ ++ int error; ++ /* DON'T TOUCH any of the rest of this stuff */ ++ int sock; ++ char buffer[MPD_BUFFER_MAX_LENGTH+1]; ++ int buflen; ++ int bufstart; ++ int doneProcessing; ++ int listOks; ++ int doneListOk; ++ int commandList; ++ mpd_ReturnElement * returnElement; ++ struct timeval timeout; ++ char *request; ++} mpd_Connection; ++ ++/* mpd_newConnection ++ * use this to open a new connection ++ * you should use mpd_closeConnection, when your done with the connection, ++ * even if an error has occurred ++ * _timeout_ is the connection timeout period in seconds ++ */ ++mpd_Connection * mpd_newConnection(const char * host, int port, float timeout); ++ ++void mpd_setConnectionTimeout(mpd_Connection * connection, float timeout); ++ ++/* mpd_closeConnection ++ * use this to close a connection and free'ing subsequent memory ++ */ ++void mpd_closeConnection(mpd_Connection * connection); ++ ++/* mpd_clearError ++ * clears error ++ */ ++void mpd_clearError(mpd_Connection * connection); ++ ++/* STATUS STUFF */ ++ ++/* use these with status.state to determine what state the player is in */ ++#define MPD_STATUS_STATE_UNKNOWN 0 ++#define MPD_STATUS_STATE_STOP 1 ++#define MPD_STATUS_STATE_PLAY 2 ++#define MPD_STATUS_STATE_PAUSE 3 ++ ++/* us this with status.volume to determine if mpd has volume support */ ++#define MPD_STATUS_NO_VOLUME -1 ++ ++/* mpd_Status ++ * holds info return from status command ++ */ ++typedef struct mpd_Status { ++ /* 0-100, or MPD_STATUS_NO_VOLUME when there is no volume support */ ++ int volume; ++ /* 1 if repeat is on, 0 otherwise */ ++ int repeat; ++ /* 1 if random is on, 0 otherwise */ ++ int random; ++ /* playlist length */ ++ int playlistLength; ++ /* playlist, use this to determine when the playlist has changed */ ++ long long playlist; ++ /* use with MPD_STATUS_STATE_* to determine state of player */ ++ int state; ++ /* crossfade setting in seconds */ ++ int crossfade; ++ /* if a song is currently selected (always the case when state is ++ * PLAY or PAUSE), this is the position of the currently ++ * playing song in the playlist, beginning with 0 ++ */ ++ int song; ++ /* Song ID of the currently selected song */ ++ int songid; ++ /* time in seconds that have elapsed in the currently playing/paused ++ * song ++ */ ++ int elapsedTime; ++ /* length in seconds of the currently playing/paused song */ ++ int totalTime; ++ /* current bit rate in kbs */ ++ int bitRate; ++ /* audio sample rate */ ++ unsigned int sampleRate; ++ /* audio bits */ ++ int bits; ++ /* audio channels */ ++ int channels; ++ /* 1 if mpd is updating, 0 otherwise */ ++ int updatingDb; ++ /* error */ ++ char * error; ++} mpd_Status; ++ ++void mpd_sendStatusCommand(mpd_Connection * connection); ++ ++/* mpd_getStatus ++ * returns status info, be sure to free it with mpd_freeStatus() ++ * call this after mpd_sendStatusCommand() ++ */ ++mpd_Status * mpd_getStatus(mpd_Connection * connection); ++ ++/* mpd_freeStatus ++ * free's status info malloc'd and returned by mpd_getStatus ++ */ ++void mpd_freeStatus(mpd_Status * status); ++ ++typedef struct _mpd_Stats { ++ int numberOfArtists; ++ int numberOfAlbums; ++ int numberOfSongs; ++ unsigned long uptime; ++ unsigned long dbUpdateTime; ++ unsigned long playTime; ++ unsigned long dbPlayTime; ++} mpd_Stats; ++ ++typedef struct _mpd_SearchStats { ++ int numberOfSongs; ++ unsigned long playTime; ++} mpd_SearchStats; ++ ++void mpd_sendStatsCommand(mpd_Connection * connection); ++ ++mpd_Stats * mpd_getStats(mpd_Connection * connection); ++ ++void mpd_freeStats(mpd_Stats * stats); ++ ++mpd_SearchStats * mpd_getSearchStats(mpd_Connection * connection); ++ ++void mpd_freeSearchStats(mpd_SearchStats * stats); ++ ++/* SONG STUFF */ ++ ++#define MPD_SONG_NO_TIME -1 ++#define MPD_SONG_NO_NUM -1 ++#define MPD_SONG_NO_ID -1 ++ ++/* mpd_Song ++ * for storing song info returned by mpd ++ */ ++typedef struct _mpd_Song { ++ /* filename of song */ ++ char * file; ++ /* artist, maybe NULL if there is no tag */ ++ char * artist; ++ /* title, maybe NULL if there is no tag */ ++ char * title; ++ /* album, maybe NULL if there is no tag */ ++ char * album; ++ /* track, maybe NULL if there is no tag */ ++ char * track; ++ /* name, maybe NULL if there is no tag; it's the name of the current ++ * song, f.e. the icyName of the stream */ ++ char * name; ++ /* date */ ++ char *date; ++ ++ /* added by qball */ ++ /* Genre */ ++ char *genre; ++ /* Composer */ ++ char *composer; ++ /* Performer */ ++ char *performer; ++ /* Disc */ ++ char *disc; ++ /* Comment */ ++ char *comment; ++ ++ /* length of song in seconds, check that it is not MPD_SONG_NO_TIME */ ++ int time; ++ /* if plchanges/playlistinfo/playlistid used, is the position of the ++ * song in the playlist */ ++ int pos; ++ /* song id for a song in the playlist */ ++ int id; ++} mpd_Song; ++ ++/* mpd_newSong ++ * use to allocate memory for a new mpd_Song ++ * file, artist, etc all initialized to NULL ++ * if your going to assign values to file, artist, etc ++ * be sure to malloc or strdup the memory ++ * use mpd_freeSong to free the memory for the mpd_Song, it will also ++ * free memory for file, artist, etc, so don't do it yourself ++ */ ++mpd_Song * mpd_newSong(void); ++ ++/* mpd_freeSong ++ * use to free memory allocated by mpd_newSong ++ * also it will free memory pointed to by file, artist, etc, so be careful ++ */ ++void mpd_freeSong(mpd_Song * song); ++ ++/* mpd_songDup ++ * works like strDup, but for a mpd_Song ++ */ ++mpd_Song * mpd_songDup(mpd_Song * song); ++ ++/* DIRECTORY STUFF */ ++ ++/* mpd_Directory ++ * used to store info fro directory (right now that just the path) ++ */ ++typedef struct _mpd_Directory { ++ char * path; ++} mpd_Directory; ++ ++/* mpd_newDirectory ++ * allocates memory for a new directory ++ * use mpd_freeDirectory to free this memory ++ */ ++mpd_Directory * mpd_newDirectory(void); ++ ++/* mpd_freeDirectory ++ * used to free memory allocated with mpd_newDirectory, and it frees ++ * path of mpd_Directory, so be careful ++ */ ++void mpd_freeDirectory(mpd_Directory * directory); ++ ++/* mpd_directoryDup ++ * works like strdup, but for mpd_Directory ++ */ ++mpd_Directory * mpd_directoryDup(mpd_Directory * directory); ++ ++/* PLAYLISTFILE STUFF */ ++ ++/* mpd_PlaylistFile ++ * stores info about playlist file returned by lsinfo ++ */ ++typedef struct _mpd_PlaylistFile { ++ char * path; ++} mpd_PlaylistFile; ++ ++/* mpd_newPlaylistFile ++ * allocates memory for new mpd_PlaylistFile, path is set to NULL ++ * free this memory with mpd_freePlaylistFile ++ */ ++mpd_PlaylistFile * mpd_newPlaylistFile(void); ++ ++/* mpd_freePlaylist ++ * free memory allocated for freePlaylistFile, will also free ++ * path, so be careful ++ */ ++void mpd_freePlaylistFile(mpd_PlaylistFile * playlist); ++ ++/* mpd_playlistFileDup ++ * works like strdup, but for mpd_PlaylistFile ++ */ ++mpd_PlaylistFile * mpd_playlistFileDup(mpd_PlaylistFile * playlist); ++ ++/* INFO ENTITY STUFF */ ++ ++/* the type of entity returned from one of the commands that generates info ++ * use in conjunction with mpd_InfoEntity.type ++ */ ++#define MPD_INFO_ENTITY_TYPE_DIRECTORY 0 ++#define MPD_INFO_ENTITY_TYPE_SONG 1 ++#define MPD_INFO_ENTITY_TYPE_PLAYLISTFILE 2 ++ ++/* mpd_InfoEntity ++ * stores info on stuff returned info commands ++ */ ++typedef struct mpd_InfoEntity { ++ /* the type of entity, use with MPD_INFO_ENTITY_TYPE_* to determine ++ * what this entity is (song, directory, etc...) ++ */ ++ int type; ++ /* the actual data you want, mpd_Song, mpd_Directory, etc */ ++ union { ++ mpd_Directory * directory; ++ mpd_Song * song; ++ mpd_PlaylistFile * playlistFile; ++ } info; ++} mpd_InfoEntity; ++ ++mpd_InfoEntity * mpd_newInfoEntity(void); ++ ++void mpd_freeInfoEntity(mpd_InfoEntity * entity); ++ ++/* INFO COMMANDS AND STUFF */ ++ ++/* use this function to loop over after calling Info/Listall functions */ ++mpd_InfoEntity * mpd_getNextInfoEntity(mpd_Connection * connection); ++ ++/* fetches the currently seeletect song (the song referenced by status->song ++ * and status->songid*/ ++void mpd_sendCurrentSongCommand(mpd_Connection * connection); ++ ++/* songNum of -1, means to display the whole list */ ++void mpd_sendPlaylistInfoCommand(mpd_Connection * connection, int songNum); ++ ++/* songId of -1, means to display the whole list */ ++void mpd_sendPlaylistIdCommand(mpd_Connection * connection, int songId); ++ ++/* use this to get the changes in the playlist since version _playlist_ */ ++void mpd_sendPlChangesCommand(mpd_Connection * connection, long long playlist); ++ ++/** ++ * @param connection: A valid and connected mpd_Connection. ++ * @param playlist: The playlist version you want the diff with. ++ * A more bandwidth efficient version of the mpd_sendPlChangesCommand. ++ * It only returns the pos+id of the changes song. ++ */ ++void mpd_sendPlChangesPosIdCommand(mpd_Connection * connection, long long playlist); ++ ++/* recursivel fetches all songs/dir/playlists in "dir* (no metadata is ++ * returned) */ ++void mpd_sendListallCommand(mpd_Connection * connection, const char * dir); ++ ++/* same as sendListallCommand, but also metadata is returned */ ++void mpd_sendListallInfoCommand(mpd_Connection * connection, const char * dir); ++ ++/* non-recursive version of ListallInfo */ ++void mpd_sendLsInfoCommand(mpd_Connection * connection, const char * dir); ++ ++#define MPD_TABLE_ARTIST MPD_TAG_ITEM_ARTIST ++#define MPD_TABLE_ALBUM MPD_TAG_ITEM_ALBUM ++#define MPD_TABLE_TITLE MPD_TAG_ITEM_TITLE ++#define MPD_TABLE_FILENAME MPD_TAG_ITEM_FILENAME ++ ++void mpd_sendSearchCommand(mpd_Connection * connection, int table, ++ const char * str); ++ ++void mpd_sendFindCommand(mpd_Connection * connection, int table, ++ const char * str); ++ ++/* LIST TAG COMMANDS */ ++ ++/* use this function fetch next artist entry, be sure to free the returned ++ * string. NULL means there are no more. Best used with sendListArtists ++ */ ++char * mpd_getNextArtist(mpd_Connection * connection); ++ ++char * mpd_getNextAlbum(mpd_Connection * connection); ++ ++char * mpd_getNextTag(mpd_Connection *connection, int type); ++ ++/* list artist or albums by artist, arg1 should be set to the artist if ++ * listing albums by a artist, otherwise NULL for listing all artists or albums ++ */ ++void mpd_sendListCommand(mpd_Connection * connection, int table, ++ const char * arg1); ++ ++/* SIMPLE COMMANDS */ ++ ++void mpd_sendAddCommand(mpd_Connection * connection, const char * file); ++ ++int mpd_sendAddIdCommand(mpd_Connection *connection, const char *file); ++ ++void mpd_sendDeleteCommand(mpd_Connection * connection, int songNum); ++ ++void mpd_sendDeleteIdCommand(mpd_Connection * connection, int songNum); ++ ++void mpd_sendSaveCommand(mpd_Connection * connection, const char * name); ++ ++void mpd_sendLoadCommand(mpd_Connection * connection, const char * name); ++ ++void mpd_sendRmCommand(mpd_Connection * connection, const char * name); ++ ++void mpd_sendRenameCommand(mpd_Connection *connection, const char *from, ++ const char *to); ++ ++void mpd_sendShuffleCommand(mpd_Connection * connection); ++ ++void mpd_sendClearCommand(mpd_Connection * connection); ++ ++/* use this to start playing at the beginning, useful when in random mode */ ++#define MPD_PLAY_AT_BEGINNING -1 ++ ++void mpd_sendPlayCommand(mpd_Connection * connection, int songNum); ++ ++void mpd_sendPlayIdCommand(mpd_Connection * connection, int songNum); ++ ++void mpd_sendStopCommand(mpd_Connection * connection); ++ ++void mpd_sendPauseCommand(mpd_Connection * connection, int pauseMode); ++ ++void mpd_sendNextCommand(mpd_Connection * connection); ++ ++void mpd_sendPrevCommand(mpd_Connection * connection); ++ ++void mpd_sendMoveCommand(mpd_Connection * connection, int from, int to); ++ ++void mpd_sendMoveIdCommand(mpd_Connection * connection, int from, int to); ++ ++void mpd_sendSwapCommand(mpd_Connection * connection, int song1, int song2); ++ ++void mpd_sendSwapIdCommand(mpd_Connection * connection, int song1, int song2); ++ ++void mpd_sendSeekCommand(mpd_Connection * connection, int song, int time); ++ ++void mpd_sendSeekIdCommand(mpd_Connection * connection, int song, int time); ++ ++void mpd_sendRepeatCommand(mpd_Connection * connection, int repeatMode); ++ ++void mpd_sendRandomCommand(mpd_Connection * connection, int randomMode); ++ ++void mpd_sendSetvolCommand(mpd_Connection * connection, int volumeChange); ++ ++/* WARNING: don't use volume command, its depreacted */ ++void mpd_sendVolumeCommand(mpd_Connection * connection, int volumeChange); ++ ++void mpd_sendCrossfadeCommand(mpd_Connection * connection, int seconds); ++ ++void mpd_sendUpdateCommand(mpd_Connection * connection, char * path); ++ ++/* returns the update job id, call this after a update command*/ ++int mpd_getUpdateId(mpd_Connection * connection); ++ ++void mpd_sendPasswordCommand(mpd_Connection * connection, const char * pass); ++ ++/* after executing a command, when your done with it to get its status ++ * (you want to check connection->error for an error) ++ */ ++void mpd_finishCommand(mpd_Connection * connection); ++ ++/* command list stuff, use this to do things like add files very quickly */ ++void mpd_sendCommandListBegin(mpd_Connection * connection); ++ ++void mpd_sendCommandListOkBegin(mpd_Connection * connection); ++ ++void mpd_sendCommandListEnd(mpd_Connection * connection); ++ ++/* advance to the next listOk ++ * returns 0 if advanced to the next list_OK, ++ * returns -1 if it advanced to an OK or ACK */ ++int mpd_nextListOkCommand(mpd_Connection * connection); ++ ++typedef struct _mpd_OutputEntity { ++ int id; ++ char * name; ++ int enabled; ++} mpd_OutputEntity; ++ ++void mpd_sendOutputsCommand(mpd_Connection * connection); ++ ++mpd_OutputEntity * mpd_getNextOutput(mpd_Connection * connection); ++ ++void mpd_sendEnableOutputCommand(mpd_Connection * connection, int outputId); ++ ++void mpd_sendDisableOutputCommand(mpd_Connection * connection, int outputId); ++ ++void mpd_freeOutputElement(mpd_OutputEntity * output); ++ ++/** ++ * @param connection a #mpd_Connection ++ * ++ * Queries mpd for the allowed commands ++ */ ++void mpd_sendCommandsCommand(mpd_Connection * connection); ++ ++/** ++ * @param connection a #mpd_Connection ++ * ++ * Queries mpd for the not allowed commands ++ */ ++void mpd_sendNotCommandsCommand(mpd_Connection * connection); ++ ++/** ++ * @param connection a #mpd_Connection ++ * ++ * returns the next supported command. ++ * ++ * @returns a string, needs to be free'ed ++ */ ++char *mpd_getNextCommand(mpd_Connection *connection); ++ ++void mpd_sendUrlHandlersCommand(mpd_Connection * connection); ++ ++char *mpd_getNextHandler(mpd_Connection * connection); ++ ++void mpd_sendTagTypesCommand(mpd_Connection * connection); ++ ++char *mpd_getNextTagType(mpd_Connection * connection); ++ ++/** ++ * @param connection a MpdConnection ++ * @param path the path to the playlist. ++ * ++ * List the content, with full metadata, of a stored playlist. ++ * ++ */ ++void mpd_sendListPlaylistInfoCommand(mpd_Connection *connection, char *path); ++ ++/** ++ * @param connection a MpdConnection ++ * @param path the path to the playlist. ++ * ++ * List the content of a stored playlist. ++ * ++ */ ++void mpd_sendListPlaylistCommand(mpd_Connection *connection, char *path); ++ ++/** ++ * @param connection a #mpd_Connection ++ * @param exact if to match exact ++ * ++ * starts a search, use mpd_addConstraintSearch to add ++ * a constraint to the search, and mpd_commitSearch to do the actual search ++ */ ++void mpd_startSearch(mpd_Connection *connection, int exact); ++ ++/** ++ * @param connection a #mpd_Connection ++ * @param type ++ * @param name ++ */ ++void mpd_addConstraintSearch(mpd_Connection *connection, int type, const char *name); ++ ++/** ++ * @param connection a #mpd_Connection ++ */ ++void mpd_commitSearch(mpd_Connection *connection); ++ ++/** ++ * @param connection a #mpd_Connection ++ * @param type The type to search for ++ * ++ * starts a search for fields... f.e. get a list of artists would be: ++ * @code ++ * mpd_startFieldSearch(connection, MPD_TAG_ITEM_ARTIST); ++ * mpd_commitSearch(connection); ++ * @endcode ++ * ++ * or get a list of artist in genre "jazz" would be: ++ * @code ++ * mpd_startFieldSearch(connection, MPD_TAG_ITEM_ARTIST); ++ * mpd_addConstraintSearch(connection, MPD_TAG_ITEM_GENRE, "jazz") ++ * mpd_commitSearch(connection); ++ * @endcode ++ * ++ * mpd_startSearch will return a list of songs (and you need mpd_getNextInfoEntity) ++ * this one will return a list of only one field (the one specified with type) and you need ++ * mpd_getNextTag to get the results ++ */ ++void mpd_startFieldSearch(mpd_Connection *connection, int type); ++ ++void mpd_startPlaylistSearch(mpd_Connection *connection, int exact); ++ ++void mpd_startStatsSearch(mpd_Connection *connection); ++ ++void mpd_sendPlaylistClearCommand(mpd_Connection *connection, char *path); ++ ++void mpd_sendPlaylistAddCommand(mpd_Connection *connection, ++ char *playlist, char *path); ++ ++void mpd_sendPlaylistMoveCommand(mpd_Connection *connection, ++ char *playlist, int from, int to); ++ ++void mpd_sendPlaylistDeleteCommand(mpd_Connection *connection, ++ char *playlist, int pos); ++#ifdef __cplusplus ++} ++#endif ++ ++#endif +diff -r 171db9560cb5 clients/mpd/mpdinterface.cc +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/clients/mpd/mpdinterface.cc Mon May 19 00:21:51 2008 -0400 +@@ -0,0 +1,404 @@ ++#include "mpdinterface.h" ++#include "immsutil.h" ++#include ++#include ++#include ++ ++namespace mpd_interface ++{ ++ position_err::position_err(int position, int playlist_length) ++ { ++ std::ostringstream s; ++ s << "Attempted to access song of invalid position in playlist. " ++ "Used position: " << position << ", playlist size: " ++ << playlist_length; ++ msg = s.str(); ++ } ++ ++ std::string Song::music_directory = ""; ++ ++ void Song::set_default_dir(std::string path) ++ { ++ music_directory = path; ++ if(!path.empty() && path[path.length()-1]!='/') { ++ music_directory.append("/"); ++ } ++ } ++ ++ bool operator==(const Song& first, const Song& second) ++ { ++ return (first.song_path == second.song_path) && ++ (first.pl_pos == second.pl_pos) && ++ (first.song_length == second.song_length); ++ } ++ bool operator!=(const Song& first, const Song& second) ++ { ++ return !(first == second); ++ } ++ ++ // determines whether the line contains the given parameter in the ++ // mpd.conf fashion ++ bool contains_parameter(const std::string& line, const std::string& parameter) ++ { ++ if(line.empty() || line[0]=='#') return false; ++ ++ std::string::size_type pos = line.find(parameter); ++ if(pos == std::string::npos) return false; ++ ++ while(pos>0) { ++ if(!isspace(line[pos-1])) return false; ++ --pos; ++ } ++ return true; ++ } ++ ++ // returns the value of the given parameter from an mpd.config line ++ std::string get_value(const std::string& line, ++ const std::string& parameter) ++ { ++ if(!contains_parameter(line, parameter)) return ""; ++ ++ std::string::size_type start = line.find_first_of("\""); ++ ++start; // position of the first non-quote char ++ if(start == std::string::npos || start>=line.size()) return ""; ++ ++ std::string::size_type end = line.find_last_of("\""); ++ if(end == std::string::npos) return ""; ++ ++ std::string result = line.substr(start, end-start); ++ return result; ++ } ++ ++ config read_configuration(std::string conf_path) throw(config_err) ++ { ++ static const std::string address_param = "bind_to_address"; ++ static const std::string port_param = "port"; ++ static const std::string dir_param = "music_directory"; ++ ++ if(conf_path == "") ++ { ++ std::string user_path = getenv("HOME"); ++ if(user_path[user_path.size()-1] != '/') user_path.append("/"); ++ user_path.append(".mpdconf"); ++ ++ try { ++ // read the "~/.mpdconf" file ++ return read_configuration(user_path); ++ } ++ catch (config_err) { // "~/.mpdconf" not found ++ // read the "/etc/mpd.conf" instead ++ return read_configuration("/etc/mpd.conf"); ++ } ++ } ++ else ++ { ++ std::ifstream con_f(conf_path.c_str()); ++ if(!con_f) { ++ throw config_err("cannot open the MPD config file \"" + ++ conf_path + "\""); ++ } ++ ++ config result; ++ std::string line; ++ while(getline(con_f, line)) { ++ if(contains_parameter(line, address_param)) { ++ result.hostname = get_value(line, address_param); ++ if(result.hostname.empty()) result.hostname = "localhost"; ++ } ++ else if(contains_parameter(line, port_param)) { ++ std::stringstream s; ++ s << get_value(line, port_param); ++ if(!(s >> result.port)) result.port = 6600; ++ } ++ else if(contains_parameter(line, dir_param)) { ++ result.music_dir = get_value(line, dir_param); ++ } ++ } ++ con_f.close(); ++ if(result.music_dir.empty()) { ++ throw config_err("infalid format of the MPD config file \"" + ++ conf_path + "\""); ++ } ++ ++ return result; ++ } ++ } ++ ++ ++ bool Server::connect(const std::string& hostname, int port) ++ throw(connection_err) ++ { ++ if(connected()) disconnect(); ++ ++ try { ++ connection = mpd_newConnection(hostname.c_str(), port, 10); ++ if(mpd_error()) throw connection_err(connection->errorStr); ++ } ++ catch(connection_err) { ++ disconnect(); ++ throw; ++ } ++ ++ if(connected()) { ++ } ++ return connected(); ++ } ++ ++ void Server::disconnect() ++ { ++ if(connection!=nullptr) { ++ mpd_closeConnection(connection); ++ } ++ connection = nullptr; ++ } ++ ++ bool Server::ack_error() const throw(connection_err) ++ { ++ if(!connected()) throw connection_err(); ++ return connection->error == MPD_ERROR_ACK; ++ } ++ // determines whether the MPD is in error state which is not the ACK error ++ bool Server::mpd_error() const throw(connection_err) ++ { ++ if(!connected()) throw connection_err(); ++ return connection->error!=0 && !ack_error(); ++ } ++ ++ // changes the mpd internal status to the playback_status type ++ playback_status resolve_state(int mpd_state) ++ { ++ switch(mpd_state) ++ { ++ case MPD_STATUS_STATE_PLAY: ++ return playing; ++ case MPD_STATUS_STATE_STOP: ++ return stopped; ++ case MPD_STATUS_STATE_PAUSE: ++ return paused; ++ default: ++ return playing; ++ } ++ } ++ ++ void Server::refresh() throw(connection_err) ++ { ++ if(!connected()) throw connection_err(); ++ try { ++ ++// the mess below is here because otherwise the results in nasty problems ++// when MPD connection times out ++//TODO prolly doesn't have to be everywhere, though. Weed it out ++if(mpd_error()) throw connection_err(connection->errorStr); ++ mpd_sendCommandListOkBegin(connection); ++if(mpd_error()) throw connection_err(connection->errorStr); ++ mpd_sendStatusCommand(connection); ++if(mpd_error()) throw connection_err(connection->errorStr); ++ mpd_sendCurrentSongCommand(connection); ++if(mpd_error()) throw connection_err(connection->errorStr); ++ mpd_sendCommandListEnd(connection); ++if(mpd_error()) throw connection_err(connection->errorStr); ++ ++ mpd_Status * status = mpd_getStatus(connection); ++ if(status!=nullptr) { ++ ++ pl_length = status->playlistLength; ++ pl_version = status->playlist; ++ pb_status = resolve_state(status->state); ++ current_song.set_elapsed(status->elapsedTime); ++ random = status->random; ++ ++ mpd_freeStatus(status); ++ } ++ ++ if(mpd_error()) throw connection_err(connection->errorStr); ++ ++ mpd_nextListOkCommand(connection); ++ mpd_InfoEntity *entity; ++ while((entity = mpd_getNextInfoEntity(connection))) { ++ mpd_Song *song = entity->info.song; ++ ++ if(entity->type!=MPD_INFO_ENTITY_TYPE_SONG) { ++ mpd_freeInfoEntity(entity); ++ continue; ++ } ++ ++ current_song.set_path(song->file); ++ current_song.set_pos(song->pos); ++ current_song.set_length(song->time); ++ mpd_freeInfoEntity(entity); ++ } ++ if(mpd_error()) throw connection_err(connection->errorStr); ++ ++ mpd_finishCommand(connection); ++ if(mpd_error()) throw connection_err(connection->errorStr); ++ } ++ catch(connection_err) { ++ disconnect(); ++ throw; ++ } ++ } ++ ++ Song Server::get_song_info(int pl_pos) ++ throw(connection_err, position_err) ++ { ++ if(!connected()) throw connection_err(); ++ if(pl_pos < 0 || pl_pos >= get_playlist_length()) { ++ throw position_err(pl_pos, get_playlist_length()); ++ } ++ ++ Song result; ++ try { ++ mpd_sendPlaylistInfoCommand(connection, pl_pos); ++ if(mpd_error()) throw connection_err(connection->errorStr); ++ ++ mpd_InfoEntity *entity; ++ while((entity = mpd_getNextInfoEntity(connection))) ++ { ++ mpd_Song *song = entity->info.song; ++ ++ if(entity->type!=MPD_INFO_ENTITY_TYPE_SONG) ++ { ++ mpd_freeInfoEntity(entity); ++ continue; ++ } ++ ++ result = Song(song->file,song->pos,song->time); ++ ++ mpd_freeInfoEntity(entity); ++ } ++ if(mpd_error()) throw connection_err(connection->errorStr); ++ ++ mpd_finishCommand(connection); ++ if(mpd_error()) throw connection_err(connection->errorStr); ++ // shouldn't happen, but just in case ++ if(ack_error()) { ++ refresh(); ++ throw position_err(pl_pos, get_playlist_length()); ++ } ++ } ++ catch(connection_err) ++ { ++ disconnect(); ++ throw; ++ } ++ ++ return result; ++ } ++ ++ void Server::play_song(int pl_pos) throw(connection_err, position_err) ++ { ++ if(!connected()) throw connection_err(); ++ if(pl_pos < 0 || pl_pos >= pl_length) { ++ throw position_err(pl_pos, get_playlist_length()); ++ } ++ ++ try { ++ mpd_sendPlayCommand(connection, pl_pos); ++ mpd_finishCommand(connection); ++ if(mpd_error()) throw connection_err(connection->errorStr); ++ if(ack_error()) throw position_err(pl_pos, get_playlist_length()); ++ } ++ catch(connection_err) { ++ disconnect(); ++ throw; ++ } ++ } ++ // end of SERVER section ++ ++ ++ // Player class ++ Player::Player(): previous_status(stopped), current_status(stopped), ++ previous_song_pos(Song::invalid_pos), current_song_pos(Song::invalid_pos), ++ null_song() ++ { ++ } ++ ++ bool Player::connect() throw(config_err, connection_err) ++ { ++ config conf = read_configuration(); ++ Song::set_default_dir(conf.music_dir); ++ return mpd.connect(conf.hostname, conf.port); ++ } ++ ++ void Player::disconnect() ++ { ++ mpd.disconnect(); ++ } ++ ++ void Player::refresh() ++ { ++ mpd.refresh(); ++ ++ playlist__changed = pl_ver != mpd.get_playlist_version(); ++ if(playlist__changed) { ++ pl_ver = mpd.get_playlist_version(); ++ ++ // update the internal playlist ++ playlist.clear(); ++ playlist.reserve(mpd.get_playlist_length()); ++ for(int i = 0; i ++#include ++ ++namespace mpd_interface ++{ ++const int nullptr = 0; ++ ++/* list of thrown exceptions */ ++ ++ // generic exception that can be thrown by MPD ++ class mpd_err ++ { ++ protected: ++ std::string msg; ++ public: ++ mpd_err(const std::string& message = "unknown error"): msg(message) { } ++ std::string message() const { return msg; } ++ }; ++ ++ // thrown when for whatever reason the connection cannot be maintained ++ // the Player is always set to the disconnected state when this is thrown ++ class connection_err: public mpd_err ++ { ++ public: ++ connection_err(const std::string& message = "not connected"): ++ mpd_err(message) { } ++ }; ++ ++ class config_err: public mpd_err ++ { ++ public: ++ config_err(const std::string& message = "") : mpd_err(message) { } ++ }; ++ ++ // thrown when trying to access element out of the playlist range ++ // contains information about the length of the playlist ++ // and the invalid position ++ class position_err: public mpd_err ++ { ++ int invalid_position; ++ int pl_length; ++ public: ++ position_err(int position, int playlist_length); ++ int pos() const { return invalid_position; } ++ int playlist_length() const {return pl_length; } ++ }; ++/* end of the exception list */ ++ ++ ++ // information about the configuration of MPD ++ // it's the relevant info read from the mpd.config file ++ struct config ++ { ++ std::string hostname; ++ int port; ++ std::string music_dir; ++ }; ++ ++ // attempts to read the configuration file ++ // if no path is specified following actions are taken: ++ // first, the ~/.mpdconf file is looked for ++ // then, the /etc/mpd.conf file is tried. ++ config read_configuration(std::string conf_path="") throw(config_err); ++ ++ ++ // Represents one song in playlist. ++ // Note that when one song is twice in a playlist (their positions differ) ++ // they are two different songs from this class' point of view. ++ class Song ++ { ++ static std::string music_directory; // directory in which the songs are ++ ++ std::string song_path; ++ int pl_pos; ++ int song_length; // time - in seconds ++ int song_elapsed; // time - in seconds ++ ++ public: ++ static const int invalid_pos = MPD_SONG_NO_NUM; ++ static const int invalid_time = MPD_SONG_NO_TIME; ++ ++ static void set_default_dir(std::string path); // set the music directory ++ ++ Song(std::string path = "", int position = invalid_pos, ++ int length = invalid_time, int elapsed = invalid_time): ++ song_path(music_directory+path), pl_pos(position), ++ song_length(length), song_elapsed(elapsed) { } ++ ++ friend bool operator==(const Song& first, const Song& second); ++ friend bool operator!=(const Song& first, const Song& second); ++ ++ std::string path() const { return song_path; } ++ void set_path(std::string path) { song_path = music_directory+path; } ++ int pos() const { return pl_pos; } ++ void set_pos(int position) { pl_pos = position; } ++ int length() const { return song_length; } ++ void set_length(int length) { song_length = length; } ++ int elapsed() const {return song_elapsed; } ++ void set_elapsed(int elapsed) { song_elapsed = elapsed; } ++ }; ++ ++ ++ ++ // the status of the playback - whether it's stopped, paused ++ // or is playing a song right now ++ enum playback_status {playing, stopped, paused} ; ++ ++ // represents the communication with the MPD server ++ // It's a confortable wrapper around the libmpdclient library ++ // When its refresh() method is called it gets all information from server ++ // and stores them so as not to flood the MPD with commands ++ class Server ++ { ++ public: ++ Server(): connection(nullptr) {}; ++ ~Server() { disconnect(); } ++ ++ bool connect(const std::string& hostname="localhost", int port = 6600) ++ throw(connection_err); ++ void disconnect(); ++ bool connected() const {return connection!=nullptr; } ++ ++ ++ void refresh() throw(connection_err); // the Server asks MPD for new values ++ int get_playlist_length() const { return pl_length; } ++ long long get_playlist_version() { return pl_version; } ++ playback_status get_playback_status() { return pb_status; } ++ bool get_random() const {return random; } ++ ++ Song get_song_info(int pl_pos) throw(connection_err,position_err); ++ Song get_current_song() const { return current_song; } ++ ++ // plays the song at the specified position ++ void play_song(int pl_pos) throw(connection_err, position_err); ++ ++ private: ++ Server& operator=(const Server&); ++ Server(const Server&); ++ ++ mpd_Connection *connection; ++ int pl_length; ++ long long pl_version; ++ playback_status pb_status; ++ Song current_song; ++ bool random; ++ ++ ++ // determines whether the actual MPD error state is the ACK error ++ bool ack_error() const throw(connection_err); ++ ++ // determines whether the MPD is in error state which is not the ACK error ++ bool mpd_error() const throw(connection_err); ++ }; ++ ++ ++ ++ enum recent {previous, current}; ++ ++ class Player ++ { ++ public: ++ ++ Player(); ++ ++ bool connect() throw(config_err, connection_err); ++ void disconnect(); ++ bool connected() const { return mpd.connected(); } ++ ++ void refresh(); ++ ++ bool song_changed() {return song__changed; } ++ bool playlist_changed() { return playlist__changed; } ++ bool status_changed() { return status__changed; } ++ ++ // gets the current or the previously played song ++ // song(current) returns currently played song ++ // song(previous) returns the previously played song ++ const Song& song(recent) const; ++ const Song& song(int position) const throw(position_err); ++ int playlist_length() const { return mpd.get_playlist_length(); } ++ bool radnom() const { return mpd.get_random(); } ++ void play_song(int pl_pos); ++ ++ // gets the current or the previous playback status ++ playback_status status(recent) const; ++ ++ ++ private: ++ Player& operator=(const Player&); ++ Player(const Player&); ++ ++ Server mpd; ++ long long pl_ver; ++ playback_status previous_status, current_status; ++ int previous_song_pos, current_song_pos; ++ const Song null_song; ++ std::vector playlist; ++ ++ bool song__changed, playlist__changed, status__changed; ++ }; ++ ++} ++ ++#endif ++ +diff -r 171db9560cb5 clients/mpd/rules.mk +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/clients/mpd/rules.mk Mon May 19 00:21:51 2008 -0400 +@@ -0,0 +1,16 @@ ++MDPCPPFLAGS= $(GLIB2CPPFLAGS) ++MPDLDFLAGS= $(GLIB2LDFLAGS) ++MPDCOMMON= mpdinterface.o libmpdclient.o clientstubbase.o libimmscore.a libmodel.a ++ ++ ++immsmpd: immsmpd.o $(MPDCOMMON) ++immsmpd: $(call objects,../clients/mpd) ++immsmpd-CPPFLAGS=$(MDPCPPFLAGS) ++immsmpd-LIBS=$(MPDLDFLAGS) ++ ++ ++MPDDESTDIR=/usr/bin ++ ++immsmpd_install: immsmpd ++ ${INSTALL} -D $^ $(MPDDESTDIR) ++ +diff -r 171db9560cb5 configure.ac +--- a/configure.ac Mon May 19 00:16:56 2008 -0400 ++++ b/configure.ac Mon May 19 00:21:51 2008 -0400 +@@ -260,6 +260,13 @@ + saved_libs="$LIBS" + + PLUGINS="" ++ ++AC_CHECK_PROG(with_mdp, mpd --help, "yes", "no") ++if test "$with_mpd" != "no"; then ++ AC_APPEND(PLUGINS, immsmpd) ++fi ++ ++ + AC_CHECK_PROG(with_xmms, xmms-config, "yes", "no") + if test "$with_xmms" != "no"; then + CPPFLAGS=`xmms-config --cflags` +diff -r 171db9560cb5 vars.mk.in +--- a/vars.mk.in Mon May 19 00:16:56 2008 -0400 ++++ b/vars.mk.in Mon May 19 00:21:51 2008 -0400 +@@ -10,7 +10,7 @@ + bindir = @bindir@ + datadir = @datadir@ + +-VPATH = ../immscore:../analyzer:../model:../autotag:../immsremote:../utils:../clients:../immsd:../data:../clients/xmms:../clients/bmp:../clients/audacious ++VPATH = ../immscore:../analyzer:../model:../autotag:../immsremote:../utils:../clients:../immsd:../data:../clients/xmms:../clients/bmp:../clients/audacious:../clients/mpd + ARFLAGS = rs + + SHELL = bash diff -r 000000000000 -r 77136249e5ee mpd-connect --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mpd-connect Mon May 19 01:28:56 2008 -0400 @@ -0,0 +1,12 @@ +diff -r 019508c19157 clients/mpd/mpdinterface.cc +--- a/clients/mpd/mpdinterface.cc Mon May 19 00:21:51 2008 -0400 ++++ b/clients/mpd/mpdinterface.cc Mon May 19 00:51:06 2008 -0400 +@@ -98,7 +98,7 @@ + conf_path + "\""); + } + +- config result; ++ config result = { "localhost", 6600, "" }; + std::string line; + while(getline(con_f, line)) { + if(contains_parameter(line, address_param)) { diff -r 000000000000 -r 77136249e5ee series --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/series Mon May 19 01:28:56 2008 -0400 @@ -0,0 +1,2 @@ +mpd-client +mpd-connect