1 /* vi:set ts=8 sts=8 sw=8:
2  *
3  * PMS  <<Practical Music Search>>
4  * Copyright (C) 2006-2010  Kim Tore Jensen
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  *
20  * command.cpp
21  * 	mediates all commands between UI and mpd
22  */
23 
24 #include "command.h"
25 #include "pms.h"
26 
27 extern Pms *			pms;
28 
29 
30 /*
31  * Status class
32  */
Mpd_status()33 Mpd_status::Mpd_status()
34 {
35 	status			= NULL;
36 	stats			= NULL;
37 
38 	muted			= false;
39 	volume			= 0;
40 	repeat			= false;
41 	single			= false;
42 	random			= false;
43 	playlist_length		= 0;
44 	playlist		= -1;
45 	storedplaylist		= -1;
46 	state			= MPD_STATUS_STATE_UNKNOWN;
47 	crossfade		= 0;
48 	song			= MPD_SONG_NO_NUM;
49 	songid			= MPD_SONG_NO_ID;
50 	time_elapsed		= 0;
51 	time_total		= 0;
52 	db_updating		= false;
53 	error			= 0;
54 	errstr.clear();
55 
56 	bitrate			= 0;
57 	samplerate		= 0;
58 	bits			= 0;
59 	channels		= 0;
60 
61 	artists_count		= 0;
62 	albums_count		= 0;
63 	songs_count		= 0;
64 
65 	uptime			= 0;
66 	db_update_time		= 0;
67 	playtime		= 0;
68 	db_playtime		= 0;
69 
70 	last_playlist		= playlist;
71 	last_state		= state;
72 	last_db_update_time	= db_update_time;
73 	last_db_updating	= db_updating;
74 	update_job_id		= -1;
75 }
76 
~Mpd_status()77 Mpd_status::~Mpd_status()
78 {
79 	if (status != NULL)
80 		mpd_freeStatus(status);
81 
82 	if (stats != NULL)
83 		mpd_freeStats(stats);
84 }
85 
assign_status(mpd_Status * st)86 void		Mpd_status::assign_status(mpd_Status * st)
87 {
88 	if (status != NULL)
89 		mpd_freeStatus(status);
90 
91 	status = st;
92 	if (status == NULL)
93 		return;
94 
95 	volume			= status->volume;
96 	repeat			= (status->repeat == 1 ? true : false);
97 	single			= (status->single == 1 ? true : false);
98 	random			= (status->random == 1 ? true : false);
99 	playlist_length		= status->playlistLength;
100 	playlist		= status->playlist;
101 	storedplaylist		= status->storedplaylist;
102 	state			= status->state;
103 	crossfade		= status->crossfade;
104 	song			= status->song;
105 	songid			= status->songid;
106 	time_elapsed		= status->elapsedTime;
107 	time_total		= status->totalTime;
108 	db_updating		= status->updatingDb;
109 	errstr			= (status->error ? status->error : "");
110 
111 	/* Audio decoded properties */
112 	bitrate			= status->bitRate;
113 	samplerate		= status->sampleRate;
114 	bits			= status->bits;
115 	channels		= status->channels;
116 }
117 
assign_stats(mpd_Stats * st)118 void		Mpd_status::assign_stats(mpd_Stats * st)
119 {
120 	if (stats != NULL)
121 		mpd_freeStats(stats);
122 
123 	stats = st;
124 	if (stats == NULL)
125 		return;
126 
127 	artists_count		= stats->numberOfArtists;
128 	albums_count		= stats->numberOfAlbums;
129 	songs_count		= stats->numberOfSongs;
130 
131 	uptime			= stats->uptime;
132 	db_update_time		= stats->dbUpdateTime;
133 	playtime		= stats->playTime;
134 	db_playtime		= stats->dbPlayTime;
135 }
136 
alive() const137 bool		Mpd_status::alive() const
138 {
139 	return (status != NULL);
140 }
141 
142 
143 
144 /*
145  * Command class manages commands sent to and from mpd
146  */
Control(Connection * n_conn)147 Control::Control(Connection * n_conn)
148 {
149 	conn = n_conn;
150 	st = new Mpd_status();
151 	rootdir = new Directory(NULL, "");
152 	_stats = NULL;
153 	_song = NULL;
154 	st->last_playlist = -1;
155 	last_song = MPD_SONG_NO_NUM;
156 	oldsong = MPD_SONG_NO_NUM;
157 	_has_new_playlist = false;
158 	_has_new_library = false;
159 	_playlist = new Songlist;
160 	_library = new Songlist;
161 	_playlist->role = LIST_ROLE_MAIN;
162 	_library->role = LIST_ROLE_LIBRARY;
163 	_active = NULL;
164 	command_mode = 0;
165 	mutevolume = 0;
166 	crossfadetime = pms->options->get_long("crossfade");
167 
168 	usetime = 0;
169 	time(&(mytime[0]));
170 	mytime[1] = 0; // Update immedately
171 }
172 
~Control()173 Control::~Control()
174 {
175 	delete _library;
176 	delete _playlist;
177 	delete st;
178 }
179 
180 /*
181  * Finishes a command and debugs any errors
182  */
finish()183 bool		Control::finish()
184 {
185 	mpd_finishCommand(conn->h());
186 	st->error = conn->h()->error;
187 	st->errstr = conn->h()->errorStr;
188 
189 	if (st->error != 0)
190 	{
191 		pms->log(MSG_CONSOLE, STERR, "MPD returned error %d: %s\n", st->error, st->errstr.c_str());
192 
193 		/* Connection closed */
194 		if (st->error == MPD_ERROR_CONNCLOSED)
195 		{
196 			conn->disconnect();
197 		}
198 
199 		clearerror();
200 
201 		return false;
202 	}
203 
204 	return true;
205 }
206 
207 /*
208  * Clears any error
209  */
clearerror()210 void		Control::clearerror()
211 {
212 	if (conn->h())
213 		mpd_clearError(conn->h());
214 }
215 
216 /*
217  * Have a usable connection?
218  */
alive()219 bool		Control::alive()
220 {
221 	return (conn != NULL && conn->connected());
222 }
223 
224 /*
225  * Reports any error from the last command
226  */
err()227 const char *	Control::err()
228 {
229 	static char * buffer = static_cast<char *>(malloc(1024));
230 
231 	if (st->errstr.size() == 0)
232 	{
233 		if (pms->msg->code == 0)
234 			sprintf(buffer, _("Error: %s"), pms->msg->str.c_str());
235 		else
236 			sprintf(buffer, _("Error %d: %s"), pms->msg->code, pms->msg->str.c_str());
237 
238 		return buffer;
239 	}
240 
241 	return st->errstr.c_str();
242 }
243 
244 /*
245  * Return authorisation level in mpd server
246  */
authlevel()247 int		Control::authlevel()
248 {
249 	int	a = AUTH_NONE;
250 
251 	if (commands.update)
252 		a += AUTH_ADMIN;
253 	if (commands.play)
254 		a += AUTH_CONTROL;
255 	if (commands.add)
256 		a += AUTH_ADD;
257 	if (commands.status)
258 		a += AUTH_READ;
259 
260 	return a;
261 }
262 
263 /*
264  * Retrieve available commands
265  */
get_available_commands()266 void		Control::get_available_commands()
267 {
268 	char *		c;
269 	string		s;
270 
271 	memset(&commands, 0, sizeof(commands));
272 
273 	mpd_sendCommandsCommand(conn->h());
274 	while ((c = mpd_getNextCommand(conn->h())) != NULL)
275 	{
276 		s = c;
277 		free(c);
278 
279 		if (s == "add")
280 			commands.add = true;
281 		else if (s == "addid")
282 			commands.addid = true;
283 		else if (s == "clear")
284 			commands.clear = true;
285 		else if (s == "clearerror")
286 			commands.clearerror = true;
287 		else if (s == "close")
288 			commands.close = true;
289 		else if (s == "commands")
290 			commands.commands = true;
291 		else if (s == "count")
292 			commands.count = true;
293 		else if (s == "crossfade")
294 			commands.crossfade = true;
295 		else if (s == "currentsong")
296 			commands.currentsong = true;
297 		else if (s == "delete")
298 			commands.delete_ = true;
299 		else if (s == "deleteid")
300 			commands.deleteid = true;
301 		else if (s == "disableoutput")
302 			commands.disableoutput = true;
303 		else if (s == "enableoutput")
304 			commands.enableoutput = true;
305 		else if (s == "find")
306 			commands.find = true;
307 		else if (s == "idle")
308 			commands.idle = true;
309 		else if (s == "kill")
310 			commands.kill = true;
311 		else if (s == "list")
312 			commands.list = true;
313 		else if (s == "listall")
314 			commands.listall = true;
315 		else if (s == "listallinfo")
316 			commands.listallinfo = true;
317 		else if (s == "listplaylist")
318 			commands.listplaylist = true;
319 		else if (s == "listplaylistinfo")
320 			commands.listplaylistinfo = true;
321 		else if (s == "listplaylists")
322 			commands.listplaylists = true;
323 		else if (s == "load")
324 			commands.load = true;
325 		else if (s == "lsinfo")
326 			commands.lsinfo = true;
327 		else if (s == "move")
328 			commands.move = true;
329 		else if (s == "moveid")
330 			commands.moveid = true;
331 		else if (s == "next")
332 			commands.next = true;
333 		else if (s == "notcommands")
334 			commands.notcommands = true;
335 		else if (s == "outputs")
336 			commands.outputs = true;
337 		else if (s == "password")
338 			commands.password = true;
339 		else if (s == "pause")
340 			commands.pause = true;
341 		else if (s == "ping")
342 			commands.ping = true;
343 		else if (s == "play")
344 			commands.play = true;
345 		else if (s == "playid")
346 			commands.playid = true;
347 		else if (s == "playlist")
348 			commands.playlist = true;
349 		else if (s == "playlistadd")
350 			commands.playlistadd = true;
351 		else if (s == "playlistclear")
352 			commands.playlistclear = true;
353 		else if (s == "playlistdelete")
354 			commands.playlistdelete = true;
355 		else if (s == "playlistfind")
356 			commands.playlistfind = true;
357 		else if (s == "playlistid")
358 			commands.playlistid = true;
359 		else if (s == "playlistinfo")
360 			commands.playlistinfo = true;
361 		else if (s == "playlistmove")
362 			commands.playlistmove = true;
363 		else if (s == "playlistsearch")
364 			commands.playlistsearch = true;
365 		else if (s == "plchanges")
366 			commands.plchanges = true;
367 		else if (s == "plchangesposid")
368 			commands.plchangesposid = true;
369 		else if (s == "previous")
370 			commands.previous = true;
371 		else if (s == "random")
372 			commands.random = true;
373 		else if (s == "rename")
374 			commands.rename = true;
375 		else if (s == "repeat")
376 			commands.repeat = true;
377 		else if (s == "single")
378 			commands.single = true;
379 		else if (s == "rm")
380 			commands.rm = true;
381 		else if (s == "save")
382 			commands.save = true;
383 		else if (s == "filter")
384 			commands.filter = true;
385 		else if (s == "seek")
386 			commands.seek = true;
387 		else if (s == "seekid")
388 			commands.seekid = true;
389 		else if (s == "setvol")
390 			commands.setvol = true;
391 		else if (s == "shuffle")
392 			commands.shuffle = true;
393 		else if (s == "stats")
394 			commands.stats = true;
395 		else if (s == "status")
396 			commands.status = true;
397 		else if (s == "stop")
398 			commands.stop = true;
399 		else if (s == "swap")
400 			commands.swap = true;
401 		else if (s == "swapid")
402 			commands.swapid = true;
403 		else if (s == "tagtypes")
404 			commands.tagtypes = true;
405 		else if (s == "update")
406 			commands.update = true;
407 		else if (s == "urlhandlers")
408 			commands.urlhandlers = true;
409 		else if (s == "volume")
410 			commands.volume = true;
411 	}
412 	finish();
413 }
414 
415 /*
416  * Play, pause, toggle, stop, next, prev
417  */
play()418 bool		Control::play()
419 {
420 	bool		r;
421 	if (!alive())	return false;
422 	mpd_sendPlayCommand(conn->h());
423 	r = finish();
424 	get_status();
425 	return r;
426 }
427 
playid(song_t songid)428 bool		Control::playid(song_t songid)
429 {
430 	bool		r;
431 	if (!alive())	return false;
432 	mpd_sendPlayIdCommand(conn->h(), songid);
433 	r = finish();
434 	get_status();
435 	return r;
436 }
437 
playpos(song_t songpos)438 bool		Control::playpos(song_t songpos)
439 {
440 	bool		r;
441 	if (!alive())	return false;
442 	mpd_sendPlayPosCommand(conn->h(), songpos);
443 	r = finish();
444 	get_status();
445 	return r;
446 }
447 
pause(bool tryplay)448 bool		Control::pause(bool tryplay)
449 {
450 	bool		r;
451 	if (!alive())	return false;
452 
453 	switch(st->state)
454 	{
455 		case MPD_STATUS_STATE_PLAY:
456 			mpd_sendPauseCommand(conn->h(), 1);
457 			break;
458 		case MPD_STATUS_STATE_PAUSE:
459 			mpd_sendPauseCommand(conn->h(), 0);
460 			break;
461 		case MPD_STATUS_STATE_STOP:
462 		case MPD_STATUS_STATE_UNKNOWN:
463 		default:
464 			return (tryplay && play());
465 	}
466 
467 	r = finish();
468 	get_status();
469 	return r;
470 }
471 
stop()472 bool		Control::stop()
473 {
474 	bool		r;
475 	if (!alive())	return false;
476 	mpd_sendStopCommand(conn->h());
477 	r = finish();
478 	get_status();
479 	return r;
480 }
481 
482 /*
483  * Shuffles the playlist.
484  */
shuffle()485 bool		Control::shuffle()
486 {
487 	bool		r;
488 	if (!alive())	return false;
489 	mpd_sendShuffleCommand(conn->h());
490 	r = finish();
491 	get_status();
492 	return r;
493 }
494 
495 /*
496  * Sets repeat mode
497  */
repeat(bool on)498 bool		Control::repeat(bool on)
499 {
500 	bool		r;
501 	if (!alive())	return false;
502 	mpd_sendRepeatCommand(conn->h(), on);
503 	r = finish();
504 	get_status();
505 	return r;
506 }
507 
508 /*
509  * Sets single mode
510  */
single(bool on)511 bool		Control::single(bool on)
512 {
513 	bool		r;
514 	if (!alive())	return false;
515 	mpd_sendSingleCommand(conn->h(), on);
516 	r = finish();
517 	get_status();
518 	return r;
519 }
520 
521 /*
522  * Set an absolute volume
523  */
setvolume(int vol)524 bool		Control::setvolume(int vol)
525 {
526 	if (!alive())	return false;
527 
528 	if (vol < 0)
529 		vol = 0;
530 	else if (vol > 100)
531 		vol = 100;
532 
533 	mpd_sendSetvolCommand(conn->h(), vol);
534 	if (finish())
535 	{
536 		st->volume = vol;
537 		return true;
538 	}
539 	return false;
540 }
541 
542 /*
543  * Changes volume
544  */
volume(int offset)545 bool		Control::volume(int offset)
546 {
547 	bool		r;
548 	if (!alive())	return false;
549 
550 	if (st->volume == MPD_STATUS_NO_VOLUME)
551 		return false;
552 
553 	if (st->volume + offset > 100)
554 		offset = 100 - st->volume;
555 	else if (st->volume + offset < 0)
556 		offset = -st->volume;
557 
558 	mpd_sendSetvolCommand(conn->h(), st->volume + offset);
559 	r = finish();
560 	if (r)
561 	{
562 		mutevolume = 0;
563 		st->volume += offset;
564 		if (st->volume < 0)
565 			st->volume = 0;
566 		else if (st->volume > 100)
567 			st->volume = 100;
568 	}
569 
570 	return r;
571 }
572 
573 /*
574  * Mute/unmute volume
575  */
mute()576 bool		Control::mute()
577 {
578 	bool		success;
579 	if (!alive())	return false;
580 
581 	if (st->volume == MPD_STATUS_NO_VOLUME)
582 		return false;
583 
584 	if (muted())
585 	{
586 		mpd_sendSetvolCommand(conn->h(), mutevolume);
587 		success = finish();
588 		if (success)
589 			st->volume = mutevolume;
590 		mutevolume = 0;
591 	}
592 	else
593 	{
594 		mutevolume = st->volume;
595 		mpd_sendSetvolCommand(conn->h(), 0);
596 		success = finish();
597 		if (success)
598 			st->volume = 0;
599 	}
600 
601 	return success;
602 }
603 
604 /*
605  * Is muted?
606  */
muted()607 bool		Control::muted()
608 {
609 	return (mutevolume > 0 && st->volume == 0);
610 }
611 
612 /*
613  * Toggles MPDs built-in random mode
614  */
random(int set)615 bool		Control::random(int set)
616 {
617 	bool		r;
618 	if (!alive())	return false;
619 
620 	if (set == -1)
621 		set = (st->random == false ? 1 : 0);
622 	else
623 		if (set > 1) set = 1;
624 
625 	mpd_sendRandomCommand(conn->h(), set);
626 	r = finish();
627 	get_status();
628 	return r;
629 }
630 
631 /*
632  * Appends a playlist to another playlist
633  */
add(Songlist * source,Songlist * dest)634 song_t		Control::add(Songlist * source, Songlist * dest)
635 {
636 	song_t			first = MPD_SONG_NO_ID;
637 	song_t			result;
638 	unsigned int		i;
639 
640 	if (source == NULL || dest == NULL)
641 		return MPD_SONG_NO_ID;
642 
643 	list_start();
644 	for (i = 0; i < source->size(); i++)
645 	{
646 		result = add(dest, source->song(i));
647 		if (first == MPD_SONG_NO_ID && result != MPD_SONG_NO_ID)
648 			first = result;
649 	}
650 	if (!list_end())
651 		return MPD_SONG_NO_ID;
652 
653 	return first;
654 }
655 
656 /*
657  * Add a song to a playlist
658  */
add(Songlist * list,Song * song)659 song_t		Control::add(Songlist * list, Song * song)
660 {
661 	song_t		i = MPD_SONG_NO_ID;
662 	Song *		nsong;
663 
664 	if (!alive() || list == NULL || song == NULL)
665 		return i;
666 
667 	if (list == _playlist)
668 	{
669 		i = mpd_sendAddIdCommand(conn->h(), song->file.c_str());
670 	}
671 	else if (list != _library)
672 	{
673 		if (list->filename.size() == 0)
674 			return i;
675 		mpd_sendPlaylistAddCommand(conn->h(), (char *)list->filename.c_str(), (char *)song->file.c_str());
676 	}
677 	else
678 	{
679 		return i;
680 	}
681 
682 	if (command_mode != 0) return i;
683 	if (finish())
684 	{
685 		nsong = new Song(song);
686 		if (list == _playlist)
687 		{
688 			nsong->id = i;
689 			nsong->pos = playlist()->size();
690 			increment();
691 		}
692 		else
693 		{
694 			nsong->id = MPD_SONG_NO_ID;
695 			nsong->pos = MPD_SONG_NO_NUM;
696 			i = list->size();
697 		}
698 		list->add(nsong);
699 	}
700 
701 	return i;
702 }
703 
704 /*
705  * Remove a song from the playlist
706  */
remove(Songlist * list,Song * song)707 int		Control::remove(Songlist * list, Song * song)
708 {
709 	int		pos = MATCH_FAILED;
710 
711 	if (!alive() || song == NULL || list == NULL)
712 		return false;
713 	if (list == _library)
714 		return false;
715 	if (list == _playlist && song->id == MPD_SONG_NO_ID)
716 		return false;
717 	if (list != _playlist)
718 	{
719 		if (list->filename.size() == 0)
720 			return false;
721 		pos = list->locatesong(song);
722 		if (pos == MATCH_FAILED)
723 			return false;
724 		pms->log(MSG_DEBUG, 0, "Removing song %d from list.\n", pos);
725 	}
726 
727 	if (list == _playlist)
728 		mpd_sendDeleteIdCommand(conn->h(), song->id);
729 	else
730 		mpd_sendPlaylistDeleteCommand(conn->h(), (char *)list->filename.c_str(), pos);
731 
732 	if (command_mode != 0) return true;
733 	if (finish())
734 	{
735 		list->remove(pos == MATCH_FAILED ? song->pos : pos);
736 		if (list == _playlist)
737 			increment();
738 		return true;
739 	}
740 
741 	return false;
742 }
743 
744 /*
745  * Crops the playlist
746  */
crop(Songlist * list,int mode)747 bool		Control::crop(Songlist * list, int mode)
748 {
749 	unsigned int		i;
750 	int			pos;
751 	unsigned int		upos;
752 	Song *			song;
753 
754 	if (!alive())		return false;
755 	if (!list)		return false;
756 	if (list == _library)
757 	{
758 		pms->msg->assign(STOK, _("The library is read-only."));
759 		return false;
760 	}
761 
762 	/* Crop to currently playing song */
763 	if (mode == CROP_PLAYING)
764 	{
765 		song = pms->cursong();
766 		if (!song)
767 		{
768 			pms->msg->assign(STOK, _("No song is playing: can't crop to playing song."));
769 			return false;
770 		}
771 
772 		pos = list->match(song->file, 0, list->end(), MATCH_FILE | MATCH_EXACT);
773 		if (pos == MATCH_FAILED)
774 		{
775 			pms->msg->assign(STOK, _("The currently playing song is not in this list."));
776 			return false;
777 		}
778 		upos = static_cast<unsigned int>(pos);
779 
780 		list_start();
781 		for (i = list->end(); i < list->size(); i--)
782 		{
783 			if (upos != i)
784 			{
785 				if (list == _playlist)
786 					mpd_sendDeleteIdCommand(conn->h(), list->song(i)->id);
787 				else
788 					mpd_sendPlaylistDeleteCommand(conn->h(), (char *)list->filename.c_str(), static_cast<int>(i));
789 				list->remove(i);
790 				increment();
791 			}
792 		}
793 		return list_end();
794 	}
795 	/* Crop to selection */
796 	else if (mode == CROP_SELECTION)
797 	{
798 		list->resetgets();
799 		if (list->getnextselected() == list->cursorsong())
800 		{
801 			if (list->getnextselected() == NULL)
802 			{
803 				list->selectsong(list->cursorsong(), true);
804 			}
805 		}
806 
807 		list_start();
808 		for (i = list->end(); i < list->size(); i--)
809 		{
810 			if (list->song(i)->selected == false)
811 			{
812 				if (list == _playlist)
813 					mpd_sendDeleteIdCommand(conn->h(), list->song(i)->id);
814 				else
815 					mpd_sendPlaylistDeleteCommand(conn->h(), (char *)list->filename.c_str(), static_cast<int>(i));
816 				list->remove(i);
817 				increment();
818 			}
819 			else
820 			{
821 				list->selectsong(list->song(i), false);
822 			}
823 		}
824 		return list_end();
825 	}
826 
827 	return false;
828 }
829 
830 /*
831  * Clears the playlist
832  */
clear(Songlist * list)833 int		Control::clear(Songlist * list)
834 {
835 	if (!alive())		return false;
836 	if (!list)		return false;
837 	if (list == _library)	return false;
838 
839 	if (list == _playlist)
840 	{
841 		mpd_sendClearCommand(conn->h());
842 		if (finish())
843 		{
844 			st->last_playlist = -1;
845 			return true;
846 		}
847 		else	return false;
848 	}
849 
850 	mpd_sendPlaylistClearCommand(conn->h(), (char *)(list->filename.c_str()));
851 	return finish();
852 }
853 
854 /*
855  * Seeks in the stream
856  */
seek(int offset)857 bool		Control::seek(int offset)
858 {
859 	if (!alive() || !song()) return false;
860 
861 	offset = st->time_elapsed + offset;
862 
863 	if (song()->id == MPD_SONG_NO_ID)
864 	{
865 		if (song()->pos == MPD_SONG_NO_NUM)
866 			return false;
867 
868 		mpd_sendSeekCommand(conn->h(), song()->pos, offset);
869 	}
870 	else
871 	{
872 		mpd_sendSeekIdCommand(conn->h(), song()->id, offset);
873 	}
874 
875 	return finish();
876 }
877 
878 /*
879  * Toggles or sets crossfading
880  */
crossfade()881 int		Control::crossfade()
882 {
883 	if (!alive()) return -1;
884 
885 	if (st->crossfade == 0)
886 	{
887 		mpd_sendCrossfadeCommand(conn->h(), crossfadetime);
888 	}
889 	else
890 	{
891 		crossfadetime = st->crossfade;
892 		mpd_sendCrossfadeCommand(conn->h(), 0);
893 	}
894 
895 	if (finish())
896 	{
897 		return (st->crossfade == 0 ? crossfadetime : 0);
898 	}
899 
900 	return -1;
901 }
902 
903 /*
904  * Set crossfade time in seconds
905  */
crossfade(int interval)906 int		Control::crossfade(int interval)
907 {
908 	if (!alive()) return false;
909 
910 	if (interval < 0)
911 		return false;
912 
913 	crossfadetime = interval;
914 	mpd_sendCrossfadeCommand(conn->h(), crossfadetime);
915 
916 	if (finish())
917 	{
918 		st->crossfade = crossfadetime;
919 		return st->crossfade;
920 	}
921 	return -1;
922 }
923 
924 /*
925  * Move selected songs
926  */
move(Songlist * list,int offset)927 unsigned int	Control::move(Songlist * list, int offset)
928 {
929 	Song *		song;
930 	int		oldpos;
931 	int		newpos;
932 	char *		filename;
933 	unsigned int	moved = 0;
934 
935 	/* Library is read only */
936 	if (list == _library || !list)
937 		return 0;
938 
939 	filename = const_cast<char *>(list->filename.c_str());
940 
941 	if (offset < 0)
942 		song = list->getnextselected();
943 	else
944 		song = list->getprevselected();
945 
946 	list_start();
947 
948 	while (song != NULL)
949 	{
950 		if (song->pos == MPD_SONG_NO_NUM)
951 		{
952 			oldpos = list->match(song->file, 0, list->end(), MATCH_FILE | MATCH_EXACT);
953 			if (oldpos == MATCH_FAILED)
954 				break;
955 		}
956 		else
957 		{
958 			oldpos = song->pos;
959 		}
960 
961 		newpos = oldpos + offset;
962 
963 		if (!list->move(oldpos, newpos))
964 			break;
965 
966 		++moved;
967 
968 		if (list != _playlist)
969 			mpd_sendPlaylistMoveCommand(conn->h(), filename, oldpos, newpos);
970 		else
971 			mpd_sendMoveCommand(conn->h(), song->pos, oldpos);
972 
973 		if (offset < 0)
974 			song = list->getnextselected();
975 		else
976 			song = list->getprevselected();
977 
978 	}
979 
980 	list->resetgets();
981 
982 	if (!list_end() || moved == 0)
983 	{
984 		return 0;
985 	}
986 
987 	if (list == _playlist)
988 	{
989 		st->last_playlist += moved;
990 	}
991 
992 	return moved;
993 }
994 
995 
996 /*
997  * Removes all songs from list1 not found in list2
998  */
prune(Songlist * list1,Songlist * list2)999 int		Control::prune(Songlist * list1, Songlist * list2)
1000 {
1001 	unsigned int		i;
1002 	int			pruned = 0;
1003 
1004 	if (!list1 || !list2) return pruned;
1005 
1006 	for (i = 0; i < list1->size(); i++)
1007 	{
1008 		if (list2->match(list1->song(i)->file, 0, list2->size() - 1, MATCH_FILE) == MATCH_FAILED)
1009 		{
1010 			pms->log(MSG_DEBUG, 0, "Pruning '%s' from list.\n", list1->song(i)->file.c_str());
1011 			list1->remove(i);
1012 			++pruned;
1013 		}
1014 	}
1015 
1016 	return pruned;
1017 }
1018 
1019 
1020 /*
1021  * Starts mpd command list/queue mode
1022  */
list_start()1023 bool		Control::list_start()
1024 {
1025 	if (!alive())	return false;
1026 
1027 	mpd_sendCommandListBegin(conn->h());
1028 	if (finish())
1029 	{
1030 		command_mode = 1;
1031 		return true;
1032 	}
1033 	return false;
1034 }
1035 
1036 /*
1037  * Ends mpd command list/queue mode
1038  */
list_end()1039 bool		Control::list_end()
1040 {
1041 	if (!alive())	return false;
1042 
1043 	mpd_sendCommandListEnd(conn->h());
1044 	if (finish())
1045 	{
1046 		command_mode = 0;
1047 		return true;
1048 	}
1049 	return false;
1050 }
1051 
1052 /*
1053  * Retrieves status about the state of MPD.
1054  */
get_status()1055 bool		Control::get_status()
1056 {
1057 	mpd_Status *	sta;
1058 	mpd_Stats *	stat;
1059 
1060 	if (!alive())	return false;
1061 
1062 	mpd_sendStatusCommand(conn->h());
1063 	sta = mpd_getStatus(conn->h());
1064 	finish();
1065 	st->assign_status(sta);
1066 
1067 	if (!st->alive())
1068 	{
1069 		pms->log(MSG_DEBUG, 0, "get_status returned NULL pointer.\n");
1070 		delete _song;
1071 		_song = NULL;
1072 		st->song = MPD_SONG_NO_NUM;
1073 		st->songid = MPD_SONG_NO_ID;
1074 		last_song = MPD_SONG_NO_ID;
1075 		return false;
1076 	}
1077 
1078 	mpd_sendStatsCommand(conn->h());
1079 	stat = mpd_getStats(conn->h());
1080 	finish();
1081 	st->assign_stats(stat);
1082 
1083 	/* Override local settings if MPD mode changed */
1084 	if (st->random)
1085 		pms->options->set_long("playmode", PLAYMODE_RANDOM);
1086 	if (st->repeat)
1087 	{
1088 		if (st->single)
1089 			pms->options->set_long("repeat", REPEAT_ONE);
1090 		else
1091 			pms->options->set_long("repeat", REPEAT_LIST);
1092 	}
1093 
1094 	if (st->db_update_time != st->last_db_update_time)
1095 	{
1096 		pms->log(MSG_DEBUG, 0, "DB time was updated from %d to %d\n", st->db_update_time, st->last_db_update_time);
1097 		pms->log(MSG_DEBUG, 0, "Server playlist version is now %d, local is %d\n", st->playlist, st->last_playlist);
1098 		st->last_db_update_time = st->db_update_time;
1099 		st->playlist = -1;
1100 		st->update_job_id = -1;
1101 		update_library();
1102 	}
1103 
1104 	return true;
1105 }
1106 
1107 /*
1108  * Query MPD server for updated information
1109  */
update(bool force)1110 int		Control::update(bool force)
1111 {
1112 	/* Need >= 1 second to update. */
1113 	time(&(mytime[usetime]));
1114 	if (!force && difftime(mytime[0], mytime[1]) == 0)
1115 	{
1116 		return 1;
1117 	}
1118 	usetime = (usetime + 1) % 2;
1119 
1120 	/* Get vital signs */
1121 	if (!get_status())
1122 	{
1123 		return -1;
1124 	}
1125 	get_current_playing();
1126 
1127 	/* New playlist? */
1128 	if (st->playlist != st->last_playlist || st->last_playlist == -1)
1129 	{
1130 		pms->log(MSG_DEBUG, 0, "Playlist needs to be updated from version %d to %d\n", st->last_playlist, st->playlist);
1131 		update_playlist();
1132 		get_status();
1133 		st->last_playlist = st->playlist;
1134 	}
1135 
1136 	return 0;
1137 }
1138 
Directory(Directory * par,string n)1139 Directory::Directory(Directory * par, string n)
1140 {
1141 	parent_ = par;
1142 	name_ = n;
1143 	cursor = 0;
1144 }
1145 
1146 /*
1147  * Return full path from top-level to here
1148  */
path()1149 string				Directory::path()
1150 {
1151 	if (parent_ == NULL)
1152 		return "";
1153 	else if (parent_->name().size() == 0)
1154 		return name_;
1155 	else
1156 		return (parent_->path() + '/' + name_);
1157 }
1158 
1159 /*
1160  * Adds a directory entry to the tree
1161  */
add(string s)1162 Directory *			Directory::add(string s)
1163 {
1164 	size_t				i;
1165 	string				t;
1166 	vector<Directory *>::iterator	it;
1167 	Directory *			d;
1168 
1169 	if (s.size() == 0)
1170 		return NULL;
1171 
1172 	i = s.find_first_of('/');
1173 
1174 	/* Within this directory */
1175 	if (i == string::npos)
1176 	{
1177 		d = new Directory(this, s);
1178 		children.push_back(d);
1179 		return d;
1180 	}
1181 
1182 	t = s.substr(0, i);		// top-level
1183 	s = s.substr(i + 1);		// all sub-level
1184 
1185 	/* Search for top-level string in subdirectories */
1186 	it = children.begin();
1187 	while (it != children.end())
1188 	{
1189 		if ((*it)->name() == t)
1190 		{
1191 			return (*it)->add(s);
1192 		}
1193 		++it;
1194 	}
1195 
1196 	/* Not found, this should _not_ happen */
1197 	pms->log(MSG_DEBUG, 0, "BUG: directory not found in hierarchy: '%s', '%s'\n", t.c_str(), s.c_str());
1198 
1199 	return NULL;
1200 }
1201 
1202 /*
1203 void		Directory::debug_tree()
1204 {
1205 	vector<Directory *>::iterator	it;
1206 	vector<Song *>::iterator	is;
1207 
1208 	pms->log(MSG_DEBUG, 0, "Printing contents of %s\n", path().c_str());
1209 
1210 	is = songs.begin();
1211 	while (is != songs.end())
1212 	{
1213 		pms->log(MSG_DEBUG, 0, "> %s\n", (*is)->file.c_str());
1214 		++is;
1215 	}
1216 
1217 	it = children.begin();
1218 	while (it != children.end())
1219 	{
1220 		(*it)->debug_tree();
1221 		++it;
1222 	}
1223 }
1224 */
1225 
1226 /*
1227  * Retrieves the entire library from MPD
1228  */
update_library()1229 void Control::update_library()
1230 {
1231 	Song *			song;
1232 	mpd_InfoEntity *	ent;
1233 	Directory *		dir = rootdir;
1234 
1235 	if (!alive())		return;
1236 
1237 	pms->log(MSG_DEBUG, 0, "Retrieving library from mpd...\n");
1238 	_library->clear();
1239 
1240 	mpd_sendListallInfoCommand(conn->h(), "/");
1241 	while ((ent = mpd_getNextInfoEntity(conn->h())) != NULL)
1242 	{
1243 		switch(ent->type)
1244 		{
1245 			case MPD_INFO_ENTITY_TYPE_SONG:
1246 				song = new Song(ent->info.song);
1247 				_library->add(song);
1248 				dir->songs.push_back(song);
1249 				break;
1250 			case MPD_INFO_ENTITY_TYPE_PLAYLISTFILE:
1251 				/* Should not receive this here. */
1252 				pms->log(MSG_DEBUG, 0, "BUG: Got playlist entity in update_library(): %s\n", ent->info.playlistFile->path);
1253 				break;
1254 			case MPD_INFO_ENTITY_TYPE_DIRECTORY:
1255 				dir = rootdir->add(ent->info.directory->path);
1256 				/* Should not be NULL, ever */
1257 				if (dir == NULL)
1258 				{
1259 					dir = rootdir;
1260 				}
1261 				break;
1262 			default:;
1263 		}
1264 		mpd_freeInfoEntity(ent);
1265 	}
1266 	finish();
1267 	update_playlists();
1268 
1269 	_has_new_library = true;
1270 }
1271 
1272 /*
1273  * Synchronizes playlists with MPD server, overwriting local versions
1274  */
update_playlists()1275 unsigned int	Control::update_playlists()
1276 {
1277 	mpd_InfoEntity *		ent;
1278 	Songlist *			list;
1279 	vector<Songlist *>		newlist;
1280 	vector<Songlist *>::iterator	i;
1281 
1282 	if (!alive()) return 0;
1283 
1284 	pms->log(MSG_DEBUG, 0, "Refreshing playlists.\n");
1285 	mpd_sendLsInfoCommand(conn->h(), "/");
1286 	while ((ent = mpd_getNextInfoEntity(conn->h())) != NULL)
1287 	{
1288 		if (ent->type == MPD_INFO_ENTITY_TYPE_PLAYLISTFILE)
1289 		{
1290 			pms->log(MSG_DEBUG, 0, "Got playlist entity: %s\n", ent->info.playlistFile->path);
1291 			list = findplaylist(ent->info.playlistFile->path);
1292 			if (!list)
1293 			{
1294 				list = new Songlist();
1295 				list->filename = ent->info.playlistFile->path;
1296 				newlist.push_back(list);
1297 			}
1298 		}
1299 		mpd_freeInfoEntity(ent);
1300 	}
1301 	finish();
1302 
1303 	retrieve_lists(newlist);
1304 	{
1305 		i = newlist.begin();
1306 		while (i != newlist.end())
1307 		{
1308 			playlists.push_back(*i);
1309 			++i;
1310 		}
1311 
1312 		pms->log(MSG_DEBUG, 0, "Server returned %d new playlists, sums to total of of %d custom playlists.\n", newlist.size(), playlists.size());
1313 	}
1314 
1315 	return playlists.size();
1316 }
1317 
1318 /*
1319  * Get all contents from server playlists playlists
1320  */
retrieve_lists(vector<Songlist * > & lists)1321 void		Control::retrieve_lists(vector<Songlist *> &lists)
1322 {
1323 	vector<Songlist *>::iterator	i;
1324 	Song *				song;
1325 	mpd_InfoEntity *		ent;
1326 
1327 	i = lists.begin();
1328 
1329 	while (i != lists.end())
1330 	{
1331 		(*i)->clear();
1332 		mpd_sendListPlaylistInfoCommand(conn->h(), (char *)(*i)->filename.c_str());
1333 		while ((ent = mpd_getNextInfoEntity(conn->h())) != NULL)
1334 		{
1335 			if (ent->type == MPD_INFO_ENTITY_TYPE_SONG)
1336 			{
1337 				song = new Song(ent->info.song);
1338 				(*i)->add(song);
1339 			}
1340 			mpd_freeInfoEntity(ent);
1341 		}
1342 		++i;
1343 	}
1344 }
1345 
1346 /*
1347  * Returns a playlist with the specified filename
1348  */
findplaylist(string fn)1349 Songlist *	Control::findplaylist(string fn)
1350 {
1351 	vector<Songlist *>::iterator	i;
1352 
1353 	i = playlists.begin();
1354 	while (i != playlists.end())
1355 	{
1356 		if ((*i)->filename == fn)
1357 		{
1358 			return *i;
1359 		}
1360 		++i;
1361 	}
1362 
1363 	return NULL;
1364 }
1365 
1366 /*
1367  * Creates or locates a new playlist
1368  */
newplaylist(string fn)1369 Songlist *	Control::newplaylist(string fn)
1370 {
1371 	Songlist *	list;
1372 
1373 	list = findplaylist(fn);
1374 	if (list != NULL)
1375 		return list;
1376 
1377 	list = new Songlist();
1378 	if (!list)
1379 		return NULL;
1380 
1381 	mpd_sendSaveCommand(conn->h(), fn.c_str());
1382 	if (!finish())
1383 	{
1384 		delete list;
1385 		return NULL;
1386 	}
1387 	pms->log(MSG_DEBUG, 0, "newplaylist(): created playlist '%s'\n", fn.c_str());
1388 	list->filename = fn;
1389 	playlists.push_back(list);
1390 	return list;
1391 }
1392 
1393 /*
1394  * Deletes a playlist
1395  */
deleteplaylist(string fn)1396 bool		Control::deleteplaylist(string fn)
1397 {
1398 	vector<Songlist *>::iterator	i;
1399 	Songlist *			lst;
1400 
1401 	i = playlists.begin();
1402 	while (i != playlists.end())
1403 	{
1404 		if ((*i)->filename == fn)
1405 		{
1406 			mpd_sendRmCommand(conn->h(), (*i)->filename.c_str());
1407 			if (finish())
1408 			{
1409 				lst = *i;
1410 				delete *i;
1411 				i = playlists.erase(i);
1412 
1413 				if (lst != _active)
1414 					return true;
1415 
1416 				/* Change active list */
1417 				if (i == playlists.end())
1418 				{
1419 					if (playlists.size() == 0)
1420 						_active = *i;
1421 					else
1422 						--i;
1423 				}
1424 
1425 				_active = *i;
1426 				return true;
1427 
1428 			}
1429 			else	return false;
1430 		}
1431 		++i;
1432 	}
1433 
1434 	return false;
1435 }
1436 
1437 /*
1438  * Returns the active playlist
1439  */
activelist()1440 Songlist *	Control::activelist()
1441 {
1442 	return _active;
1443 }
1444 
1445 /*
1446  * Sets the active playlist
1447  */
activatelist(Songlist * list)1448 bool		Control::activatelist(Songlist * list)
1449 {
1450 	vector<Songlist *>::iterator	i;
1451 	bool				changed = false;
1452 
1453 	if (list == _playlist || list == _library)
1454 	{
1455 		_active = list;
1456 		changed = true;
1457 	}
1458 	else
1459 	{
1460 		i = playlists.begin();
1461 		while (i != playlists.end())
1462 		{
1463 			if (*i == list)
1464 			{
1465 				_active = list;
1466 				changed = true;
1467 				break;
1468 			}
1469 			++i;
1470 		}
1471 	}
1472 
1473 	/* Have MPD manage random inside playlist */
1474 	if (changed)
1475 	{
1476 		repeat((pms->options->get_long("repeat") == REPEAT_LIST || pms->options->get_long("repeat") == REPEAT_ONE) && activelist() == playlist());
1477 		single(pms->options->get_long("repeat") == REPEAT_ONE && activelist() == playlist());
1478 		random(pms->options->get_long("playmode") == PLAYMODE_RANDOM && activelist() == playlist());
1479 	}
1480 
1481 	return changed;
1482 }
1483 
1484 /*
1485  * Retrieves current playlist from MPD
1486  */
update_playlist()1487 void		Control::update_playlist()
1488 {
1489 	Song			*song;
1490 	mpd_InfoEntity		*ent;
1491 
1492 	if (!alive())		return;
1493 
1494 	pms->log(MSG_DEBUG, 0, "Quering playlist changes.\n");
1495 
1496 	if (st->last_playlist == -1)
1497 	{
1498 		_playlist->clear();
1499 	}
1500 
1501 	mpd_sendPlChangesCommand(conn->h(), st->last_playlist);
1502 	while ((ent = mpd_getNextInfoEntity(conn->h())) != NULL)
1503 	{
1504 		song = new Song(ent->info.song);
1505 		_playlist->add(song);
1506 		mpd_freeInfoEntity(ent);
1507 	}
1508 	finish();
1509 
1510 	_playlist->truncate(st->playlist_length);
1511 
1512 	_has_new_playlist = true;
1513 }
1514 
1515 /*
1516  * Info for display class whether playlist has changed and needs a redraw
1517  */
has_new_library()1518 bool Control::has_new_library()
1519 {
1520 	if (_has_new_library)
1521 	{
1522 		_has_new_library = false;
1523 		return true;
1524 	}
1525 	return false;
1526 }
has_new_playlist()1527 bool Control::has_new_playlist()
1528 {
1529 	if (_has_new_playlist)
1530 	{
1531 		_has_new_playlist = false;
1532 		return true;
1533 	}
1534 	return false;
1535 }
1536 
1537 /*
1538  * Tells whether the currently playing song has changed since last call
1539  */
song_changed()1540 bool		Control::song_changed()
1541 {
1542 	if (!alive())	return false;
1543 	if (last_song == oldsong)
1544 		return false;
1545 
1546 	oldsong = last_song;
1547 	return true;
1548 }
1549 
1550 /*
1551  * Tells whether the play state changed since last call
1552  */
state_changed()1553 bool		Control::state_changed()
1554 {
1555 	if (!alive() || st->last_state == st->state)
1556 		return false;
1557 
1558 	st->last_state = st->state;
1559 	return true;
1560 }
1561 
1562 
1563 /*
1564  * Stores the currently playing song in _song
1565  */
get_current_playing()1566 int Control::get_current_playing()
1567 {
1568 	mpd_InfoEntity		*ent;
1569 
1570 	if (!alive())
1571 	{
1572 		return MPD_SONG_NO_ID;
1573 	}
1574 	mpd_sendCurrentSongCommand(conn->h());
1575 
1576 	ent = mpd_getNextInfoEntity(conn->h());
1577 	if (ent == NULL || ent->type != MPD_INFO_ENTITY_TYPE_SONG)
1578 	{
1579 		_has_new_playlist = true;
1580 		last_song = MPD_SONG_NO_NUM;
1581 		_song = NULL;
1582 		return MPD_SONG_NO_ID;
1583 	}
1584 
1585 	if (_song != NULL)
1586 		delete _song;
1587 
1588 	_song = new Song(ent->info.song);
1589 
1590 	if (_song->id != last_song)
1591 	{
1592 		_has_new_playlist = true;
1593 		oldsong = last_song;
1594 		last_song = _song->id;
1595 	}
1596 
1597 	mpd_freeInfoEntity(ent);
1598 	finish();
1599 
1600 	return 0;
1601 }
1602 
1603 /*
1604  * Rescans entire library
1605  */
rescandb(string dest)1606 bool		Control::rescandb(string dest)
1607 {
1608 	if (!alive())		return false;
1609 	if (st->db_updating)	return false;
1610 
1611 	mpd_sendUpdateCommand(conn->h(), dest.c_str());
1612 	st->update_job_id = mpd_getUpdateId(conn->h());
1613 
1614 	return finish();
1615 }
1616 
1617 /*
1618  * Sends a password to the mpd server
1619  */
sendpassword(string pw)1620 bool		Control::sendpassword(string pw)
1621 {
1622 	if (!alive())		return false;
1623 	if (pw.size() == 0)	return false;
1624 
1625 	mpd_sendPasswordCommand(conn->h(), pw.c_str());
1626 	return finish();
1627 }
1628 
1629 /*
1630  * Notifies command system that an update from server is unneccessary as PMS already has done it.
1631  */
increment()1632 bool		Control::increment()
1633 {
1634 	if (st->last_playlist == -1)
1635 	{
1636 		return false;
1637 	}
1638 	++(st->last_playlist);
1639 	return true;
1640 }
1641 
1642