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