1 /*
2  *      Copyright (C) 2019 Jean-Luc Barriere
3  *
4  *  This file is part of Noson-App
5  *
6  *  Noson-App 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  *  Noson 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 Foobar.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 #if (defined(_WIN32) || defined(_WIN64))
22 #define __WINDOWS__
23 #endif
24 
25 #ifdef __WINDOWS__
26 #include <WinSock2.h>
27 #include <Windows.h>
28 #include <time.h>
29 #define usleep(t) Sleep((DWORD)(t)/1000)
30 #define sleep(t)  Sleep((DWORD)(t)*1000)
31 #else
32 #include <unistd.h>
33 #include <sys/time.h>
34 #endif
35 
36 #include <noson/sonossystem.h>
37 #include <noson/sonosplayer.h>
38 #include <noson/contentdirectory.h>
39 #include <noson/didlparser.h>
40 #include <noson/imageservice.h>
41 #include <noson/filestreamer.h>
42 #ifdef HAVE_PULSEAUDIO
43 #include <noson/pulsestreamer.h>
44 #endif
45 
46 #include <cstdlib>
47 #include <cstdio>
48 #include <iostream>
49 #include <string>
50 #include <algorithm> // std::find
51 
52 #include "tokenizer.h"
53 #include "builtin.h"
54 
55 #ifdef __WINDOWS__
56 #define LASTERROR WSAGetLastError()
57 #define ERRNO_INTR WSAEINTR
58 #define FLUSHOUT() fflush(stderr);
59 #define PRINT(a) fprintf(stderr, a)
60 #define PRINT1(a,b) fprintf(stderr, a, b)
61 #define PRINT2(a,b,c) fprintf(stderr, a, b, c)
62 #define PRINT3(a,b,c,d) fprintf(stderr, a, b, c, d)
63 #define PRINT4(a,b,c,d,e) fprintf(stderr, a, b, c, d, e)
64 #else
65 #define LASTERROR errno
66 #define ERRNO_INTR EINTR
67 #define FLUSHOUT() fflush(stdout);
68 #define PRINT(a) fprintf(stdout, a)
69 #define PRINT1(a,b) fprintf(stdout, a, b)
70 #define PRINT2(a,b,c) fprintf(stdout, a, b, c)
71 #define PRINT3(a,b,c,d) fprintf(stdout, a, b, c, d)
72 #define PRINT4(a,b,c,d,e) fprintf(stdout, a, b, c, d, e)
73 #endif
74 #define PERROR(a) fprintf(stderr, a)
75 #define PERROR1(a,b) fprintf(stderr, a, b)
76 #define PERROR2(a,b,c) fprintf(stderr, a, b, c)
77 #define PERROR3(a,b,c,d) fprintf(stderr, a, b, c, d)
78 
79 static const char * getCmd(char **begin, char **end, const std::string& option);
80 static const char * getCmdOption(char **begin, char **end, const std::string& option);
81 static void readInStream();
82 
83 SONOS::System * gSonos = 0;
84 SONOS::PlayerPtr gPlayer;
85 int gDebug = 0;
86 
handleEventCB(void * handle)87 void handleEventCB(void* handle)
88 {
89   if (gDebug > 2)
90   {
91     unsigned char mask = gSonos->LastEvents();
92     if ((mask & SONOS::SVCEvent_AlarmClockChanged))
93       fprintf(stderr, "AlarmClockChanged event triggered\n");
94     if ((mask & SONOS::SVCEvent_ZGTopologyChanged))
95       fprintf(stderr, "ZGTopologyChanged event triggered\n");
96   }
97 }
98 
99 /*
100  * the main function
101  */
main(int argc,char ** argv)102 int main(int argc, char** argv)
103 {
104   int ret = 0;
105   SONOS::System::Debug(0);
106 
107   if (getCmd(argv, argv + argc, "--help") || getCmd(argv, argv + argc, "-h"))
108   {
109     PRINT("\n  --deviceurl <URL>\n\n");
110     PRINT("  Bypass the SSDP discovery by connecting to an endpoint. The typical URLs are:\n");
111     PRINT("  http://{IPADDRESS}:1400 or http://{IPADDRESS}:3400\n");
112     PRINT("\n  --debug\n\n");
113     PRINT("  Enable the debug output.\n");
114     PRINT("\n  --help | -h\n\n");
115     PRINT("  Print the command usage.\n\n");
116     return EXIT_SUCCESS;
117   }
118 
119   if (getCmd(argv, argv + argc, "--debug"))
120     gDebug = 4;
121   SONOS::System::Debug(gDebug);
122 
123   const char* deviceUrl = getCmdOption(argv, argv + argc, "--deviceurl");
124 
125 #ifdef __WINDOWS__
126   //Initialize Winsock
127   WSADATA wsaData;
128   if ((ret = WSAStartup(MAKEWORD(2, 2), &wsaData)))
129     return ret;
130 #endif /* __WINDOWS__ */
131 
132   PRINT1("Noson CLI using libnoson %s, Copyright (C) 2018 Jean-Luc Barriere\n", SONOS::libVersionString());
133   gSonos = new SONOS::System(0, handleEventCB);
134   if (!deviceUrl)
135   {
136     PERROR("Searching... ");
137     if (!gSonos->Discover())
138     {
139       PERROR("No SONOS zone found.\n");
140       return EXIT_FAILURE;
141     }
142     else
143       PERROR("Succeeded\n");
144   }
145   else
146   {
147     PERROR1("Connecting to %s... ", deviceUrl);
148     if (!gSonos->Discover(deviceUrl))
149     {
150       PERROR("The SONOS device is unreachable.\n");
151       return EXIT_FAILURE;
152     }
153     else
154       PERROR("Succeeded\n");
155   }
156 
157   /*
158    * Register handlers to process remote request
159    */
160   {
161     SONOS::RequestBrokerPtr imageService(new SONOS::ImageService());
162     gSonos->RegisterRequestBroker(imageService);
163 #ifdef HAVE_PULSEAUDIO
164     gSonos->RegisterRequestBroker(SONOS::RequestBrokerPtr(new SONOS::PulseStreamer(imageService.get())));
165 #endif
166     gSonos->RegisterRequestBroker(SONOS::RequestBrokerPtr(new SONOS::FileStreamer()));
167   }
168   /*
169    * Print Players list
170    */
171   SONOS::ZonePlayerList players = gSonos->GetZonePlayerList();
172   for (SONOS::ZonePlayerList::const_iterator it = players.begin(); it != players.end(); ++it)
173     PRINT2("Found player '%s' with UUID '%s'\n", it->first.c_str(), it->second->GetUUID().c_str());
174   /*
175    * Print Zones list and connect to
176    */
177   SONOS::ZoneList zones = gSonos->GetZoneList();
178   for (SONOS::ZoneList::const_iterator it = zones.begin(); it != zones.end(); ++it)
179     PRINT2("Found zone '%s' with coordinator '%s'\n", it->second->GetZoneName().c_str(), it->second->GetCoordinator()->c_str());
180 
181   readInStream();
182 
183   if (gPlayer)
184     gPlayer.reset();
185   delete gSonos;
186   gSonos = 0;
187 
188 #ifdef __WINDOWS__
189   WSACleanup();
190 #endif /* __WINDOWS__ */
191   return ret;
192 }
193 
getCmd(char ** begin,char ** end,const std::string & option)194 static const char * getCmd(char **begin, char **end, const std::string& option)
195 {
196   char **itr = std::find(begin, end, option);
197   if (itr != end)
198   {
199     return *itr;
200   }
201   return NULL;
202 }
203 
getCmdOption(char ** begin,char ** end,const std::string & option)204 static const char * getCmdOption(char **begin, char **end, const std::string& option)
205 {
206   for (char** it = begin; it != end; ++it)
207   {
208     if (strncmp(*it, option.c_str(), option.length()) == 0 && (*it)[option.length()] == '=')
209       return &((*it)[option.length() + 1]);
210   }
211   return NULL;
212 }
213 
upstr(std::string & str)214 std::string& upstr(std::string& str)
215 {
216   std::string::iterator c = str.begin();
217   while (c != str.end())
218   {
219     *c = toupper(*c);
220     ++c;
221   }
222   return str;
223 }
224 
parseCommand(const std::string & line)225 static bool parseCommand(const std::string& line)
226 {
227   std::vector<std::string> tokens;
228   tokenize(line, " ", tokens, true);
229   std::vector<std::string>::const_iterator it = tokens.begin();
230   if (it != tokens.end())
231   {
232     std::string token(*it);
233     upstr(token);
234 
235     if (token == "EXIT")
236       return false;
237     else if (token == "")
238     {}
239     else if (token == "HELP")
240     {
241       PRINT("EXIT                          Exit from CLI\n");
242       PRINT("CONNECT {zone name}           Connect to a zone for control\n");
243       PRINT("STATUS                        Show the playing status\n");
244 #ifdef HAVE_PULSEAUDIO
245       PRINT("PLAYPULSE                     Play stream from Pulse\n");
246 #endif
247       PRINT("PLAYURL {stream URL}          Play stream from URL\n");
248       PRINT("PLAYFV {URI}                  Play the given favorite\n");
249       PRINT("PLAYSQ {URI}                  Play the given playlist\n");
250       PRINT("PLAYQUEUE                     Play queue\n");
251       PRINT("PLAYLINEIN                    Play line-IN\n");
252       PRINT("PLAYDIGITALIN                 Play digital-IN/TV\n");
253       PRINT("PLAY                          Press play\n");
254       PRINT("PAUSE                         Press pause\n");
255       PRINT("STOP                          Press stop\n");
256       PRINT("PREVIOUS                      Press skip previous\n");
257       PRINT("NEXT                          Press skip next\n");
258       PRINT("SEEK 1..                      Seek to track number\n");
259       PRINT("VOLUME 0..100                 Set volume master\n");
260       PRINT("VOLUME {player} 0..100        Set volume\n");
261       PRINT("SLEEPTIMER 0..65535           Set sleep timer\n");
262       PRINT("SHOWQUEUE                     Show queue content\n");
263       PRINT("SHOWFV                        Show favorites\n");
264       PRINT("SHOWSQ                        Show playlists\n");
265       PRINT("SHOWAC                        Show alarms clock\n");
266       PRINT("CREATEAC {1} {2} {3} {4} {5}  Create alarm clock using arguments:\n");
267       PRINT("  1:ROOM       The room UUID\n");
268       PRINT("  2:STARTTIME  The time using format HH:MM:SS\n");
269       PRINT("  3:RECURRENCE The comma separated values of day: SUN,MON,..,SAT\n");
270       PRINT("  4:DURATION   The duration using format HH:MM:SS\n");
271       PRINT("  5:VOLUME     0..100\n");
272       PRINT("ENABLEAC {id}                 Enable alarm clock\n");
273       PRINT("DISABLEAC {id}                Disable alarm clock\n");
274       PRINT("DESTROYAC {id}                Destroy alarm clock\n");
275       PRINT("UPDATEAC {id} {2} {3}         Update alarm clock using arguments:\n");
276       PRINT("  2:Type ROOM,STARTTIME,RECURRENCE,DURATION,VOLUME,PROGRAM\n");
277       PRINT("  3:New value\n");
278       PRINT("  Program 0 for Buzzer or the index of favorite (see SHOWFV)\n");
279       PRINT("REFRESHSHAREINDEX             Update the music index on Sonos\n");
280       PRINT("HELP                          Print this help\n");
281       PRINT("\n");
282     }
283     else if (token == "CONNECT")
284     {
285       if (++it != tokens.end())
286       {
287         std::string param(*it);
288         while(++it != tokens.end())
289           param.append(" ").append(*it);
290         SONOS::ZoneList zones = gSonos->GetZoneList();
291         bool found = false;
292         for (SONOS::ZoneList::const_iterator iz = zones.begin(); iz != zones.end(); ++iz)
293         {
294           if (iz->second->GetZoneName() == param)
295           {
296             found = true;
297             if ((gPlayer = gSonos->GetPlayer(iz->second, 0, 0)))
298               PERROR1("Connected to zone %s\n", gPlayer->GetZone()->GetZoneName().c_str());
299             else
300               PERROR("Failed\n");
301             break;
302           }
303         }
304         if (!found)
305           PERROR("Not found\n");
306       }
307       else
308         PERROR("Error: Missing arguments.\n");
309     }
310     else if (!gSonos->IsConnected() || !gPlayer)
311     {
312       PERROR("Error: Not connected.\n");
313     }
314     else if (token == "STATUS")
315     {
316       while (gPlayer->TransportPropertyEmpty())
317         sleep(1);
318       SONOS::AVTProperty props = gPlayer->GetTransportProperty();
319       PRINT1("TransportStatus = %s\n", props.TransportStatus.c_str());
320       PRINT1("TransportState = %s\n", props.TransportState.c_str());
321       PRINT1("AVTransportURI = [%s]\n", props.AVTransportURI.c_str());
322       PRINT1("AVTransportTitle = [%s]\n", props.AVTransportURIMetaData ? props.AVTransportURIMetaData->GetValue("dc:title").c_str() : "null");
323       PRINT1("CurrentTrack = %d\n", props.CurrentTrack);
324       PRINT1("CurrentTrackDuration = %s\n", props.CurrentTrackDuration.c_str());
325       PRINT1("CurrentTrackURI = [%s]\n", props.CurrentTrackURI.c_str());
326       PRINT1("CurrentTrackTitle = [%s]\n", props.CurrentTrackMetaData ? props.CurrentTrackMetaData->GetValue("dc:title").c_str() : "null");
327       PRINT1("CurrentTrackAlbum = [%s]\n", props.CurrentTrackMetaData ? props.CurrentTrackMetaData->GetValue("upnp:album").c_str() : "null");
328       PRINT1("CurrentTrackArtist = [%s]\n", props.CurrentTrackMetaData ? props.CurrentTrackMetaData->GetValue("dc:creator").c_str() : "null");
329       PRINT1("CurrentCrossfadeMode = %s\n", props.CurrentCrossfadeMode.c_str());
330       PRINT1("CurrentPlayMode = %s\n", props.CurrentPlayMode.c_str());
331       PRINT1("CurrentTransportActions = %s\n", props.CurrentTransportActions.c_str());
332       PRINT1("NumberOfTracks = %d\n", props.NumberOfTracks);
333       PRINT1("AlarmRunning = %s\n", props.r_AlarmRunning.c_str());
334       PRINT1("AlarmIDRunning = %s\n", props.r_AlarmIDRunning.c_str());
335       PRINT1("AlarmLoggedStartTime = %s\n", props.r_AlarmLoggedStartTime.c_str());
336       PRINT1("AlarmState = %s\n", props.r_AlarmState.c_str());
337 
338       SONOS::ElementList vars;
339       if (gPlayer->GetRemainingSleepTimerDuration(vars))
340       {
341         PRINT1("RemainingSleepTimerDuration = %s\n", vars.GetValue("RemainingSleepTimerDuration").c_str());
342       }
343     }
344     else if (token == "SLEEPTIMER")
345     {
346       if (++it != tokens.end())
347       {
348         std::string param(*it);
349         uint16_t value = 0;
350         string_to_uint16(param.c_str(), &value);
351         if (gPlayer->ConfigureSleepTimer((unsigned)value))
352           PERROR("Succeeded\n");
353         else
354           PERROR("Failed\n");
355       }
356       else
357         PERROR("Error: Missing arguments.\n");
358     }
359     else if (token == "SHOWAC")
360     {
361       SONOS::AlarmList alarms = gSonos->GetAlarmList();
362       for (SONOS::AlarmList::const_iterator il = alarms.begin(); il != alarms.end(); ++il)
363       {
364         PRINT("\n");
365         PRINT2("%s: Enabled = %s\n", (*il)->GetId().c_str(), (*il)->GetEnabled() ? "true" : "false");
366         PRINT2("%s: StartTime = %s\n", (*il)->GetId().c_str(), (*il)->GetStartLocalTime().c_str());
367         PRINT2("%s: Recurrence = %s\n", (*il)->GetId().c_str(), (*il)->GetRecurrence().c_str());
368         PRINT2("%s: RoomUUID = %s\n", (*il)->GetId().c_str(), (*il)->GetRoomUUID().c_str());
369         PRINT2("%s: IncludeLinkedZones = %s\n", (*il)->GetId().c_str(), (*il)->GetIncludeLinkedZones() ? "true" : "false");
370         PRINT2("%s: ProgramURI = %s\n", (*il)->GetId().c_str(), (*il)->GetProgramURI().c_str());
371         const SONOS::DigitalItemPtr didl = (*il)->GetProgramMetadata();
372         PRINT2("%s: ProgramTitle = %s\n", (*il)->GetId().c_str(), didl ? didl->GetValue("dc:title").c_str() : "");
373         PRINT2("%s: PlayMode = %s\n", (*il)->GetId().c_str(), (*il)->GetPlayMode().c_str());
374         PRINT2("%s: Volume = %d\n", (*il)->GetId().c_str(), (*il)->GetVolume());
375         PRINT2("%s: Duration = %s\n", (*il)->GetId().c_str(), (*il)->GetDuration().c_str());
376       }
377     }
378     else if (token == "CREATEAC")
379     {
380       std::string roomUUID;
381       std::string start;
382       std::string recurrence("MON,TUE,WED,THU,FRI");
383       std::string duration("01:00:00");
384       uint8_t volume = 20;
385       if (++it != tokens.end())
386         roomUUID.assign(*it);
387       if (++it != tokens.end())
388         start.assign(*it);
389       if (it != tokens.end() && ++it != tokens.end())
390         recurrence.assign(*it);
391       if (it != tokens.end() && ++it != tokens.end())
392         duration.assign(*it);
393       if (it != tokens.end() && ++it != tokens.end())
394         string_to_uint8(it->c_str(), &volume);
395       if (it != tokens.end() && ++it == tokens.end())
396       {
397         SONOS::Alarm alarm;
398         alarm.SetRoomUUID(roomUUID);
399         alarm.SetStartLocalTime(start);
400         alarm.SetRecurrence(recurrence);
401         alarm.SetDuration(duration);
402         alarm.SetVolume(volume);
403         if (gSonos->CreateAlarm(alarm))
404           PERROR("Succeeded\n");
405         else
406           PERROR("Failed\n");
407       }
408       else
409         PERROR("Error: Missing arguments.\n");
410     }
411     else if (token == "ENABLEAC")
412     {
413       if (++it != tokens.end())
414       {
415         SONOS::AlarmList alarms = gSonos->GetAlarmList();
416         SONOS::AlarmPtr ptr;
417         for (SONOS::AlarmList::iterator il = alarms.begin(); il != alarms.end(); ++il)
418         {
419           if ((*il)->GetId() == *it)
420           {
421             ptr = *il;
422             break;
423           }
424         }
425         if (ptr)
426         {
427           ptr->SetEnabled(true);
428           if (gSonos->UpdateAlarm(*ptr))
429             PERROR("Succeeded\n");
430           else
431             PERROR("Failed\n");
432         }
433         else
434           PERROR("Error: Invalid alarm ID.\n");
435       }
436       else
437         PERROR("Error: Missing arguments.\n");
438     }
439     else if (token == "DISABLEAC")
440     {
441       if (++it != tokens.end())
442       {
443         SONOS::AlarmList alarms = gSonos->GetAlarmList();
444         SONOS::AlarmPtr ptr;
445         for (SONOS::AlarmList::iterator il = alarms.begin(); il != alarms.end(); ++il)
446         {
447           if ((*il)->GetId() == *it)
448           {
449             ptr = *il;
450             break;
451           }
452         }
453         if (ptr)
454         {
455           ptr->SetEnabled(false);
456           if (gSonos->UpdateAlarm(*ptr))
457             PERROR("Succeeded\n");
458           else
459             PERROR("Failed\n");
460         }
461         else
462           PERROR("Error: Invalid alarm ID.\n");
463       }
464       else
465         PERROR("Error: Missing arguments.\n");
466     }
467     else if (token == "DESTROYAC")
468     {
469       if (++it != tokens.end())
470       {
471         if (gSonos->DestroyAlarm(*it))
472           PERROR("Succeeded\n");
473         else
474           PERROR("Failed\n");
475       }
476       else
477         PERROR("Error: Missing arguments.\n");
478     }
479     else if (token == "UPDATEAC")
480     {
481       std::string alarmId;
482       std::string type;
483       if (++it != tokens.end())
484         alarmId.assign(*it);
485       if (it != tokens.end() && ++it != tokens.end())
486       {
487         type.assign(*it);
488         upstr(type);
489       }
490       if (it != tokens.end() && ++it != tokens.end())
491       {
492         SONOS::AlarmList alarms = gSonos->GetAlarmList();
493         SONOS::AlarmPtr ptr;
494         for (SONOS::AlarmList::iterator il = alarms.begin(); il != alarms.end(); ++il)
495         {
496           if ((*il)->GetId() == alarmId)
497           {
498             ptr = *il;
499             break;
500           }
501         }
502         if (ptr)
503         {
504           //ROOM,TIME,RECURRENCE,DURATION,VOLUME
505           if (type == "ROOM")
506             ptr->SetRoomUUID(*it);
507           else if (type == "STARTTIME")
508             ptr->SetStartLocalTime(*it);
509           else if (type == "RECURRENCE")
510             ptr->SetRecurrence(*it);
511           else if (type == "DURATION")
512             ptr->SetDuration(*it);
513           else if (type == "VOLUME")
514           {
515             uint8_t value = 0;
516             string_to_uint8(it->c_str(), &value);
517             ptr->SetVolume(value);
518           }
519           else if (type == "PROGRAM")
520           {
521             if (*it == "0")
522             {
523               ptr->SetProgramURI(ALARM_BUZZER_URI);
524               ptr->SetProgramMetadata(SONOS::DigitalItemPtr());
525             }
526             else
527             {
528               uint16_t value = 0;
529               string_to_uint16(it->c_str(), &value);
530               SONOS::ContentDirectory mycontent(gSonos->GetHost(), gSonos->GetPort());
531               SONOS::ContentList bdir(mycontent, "FV:2");
532               SONOS::ContentList::iterator ic = bdir.begin();
533               uint16_t i = 0;
534               while (ic != bdir.end())
535               {
536                 if (++i == value)
537                 {
538                   SONOS::DigitalItemPtr metaPtr;
539                   if (SONOS::System::ExtractObjectFromFavorite(*ic, metaPtr))
540                   {
541                     ptr->SetProgramURI(metaPtr->GetValue("res"));
542                     ptr->SetProgramMetadata(metaPtr);
543                   }
544                   break;
545                 }
546                 ++ic;
547               }
548             }
549           }
550           if (gSonos->UpdateAlarm(*ptr))
551             PERROR("Succeeded\n");
552           else
553             PERROR("Failed\n");
554         }
555         else
556           PERROR("Error: Invalid alarm ID.\n");
557       }
558       else
559         PERROR("Error: Missing arguments.\n");
560     }
561     else if (token == "PLAY")
562     {
563       if (gPlayer->Play())
564         PERROR("Succeeded\n");
565       else
566         PERROR("Failed\n");
567     }
568     else if (token == "STOP")
569     {
570       if (gPlayer->Stop())
571         PERROR("Succeeded\n");
572       else
573         PERROR("Failed\n");
574     }
575     else if (token == "PAUSE")
576     {
577       if (gPlayer->Pause())
578         PERROR("Succeeded\n");
579       else
580         PERROR("Failed\n");
581     }
582     else if (token == "PREVIOUS")
583     {
584       if (gPlayer->Previous())
585         PERROR("Succeeded\n");
586       else
587         PERROR("Failed\n");
588     }
589     else if (token == "NEXT")
590     {
591       if (gPlayer->Next())
592         PERROR("Succeeded\n");
593       else
594         PERROR("Failed\n");
595     }
596     else if (token == "SEEK")
597     {
598       if (++it != tokens.end())
599       {
600         uint32_t value = 0;
601         string_to_uint32(it->c_str(), &value);
602         if (gPlayer->SeekTrack(value))
603           PERROR("Succeeded\n");
604         else
605           PERROR("Failed\n");
606       }
607       else
608         PERROR("Error: Missing arguments.\n");
609     }
610 #ifdef HAVE_PULSEAUDIO
611     else if (token == "PLAYPULSE")
612     {
613       if (gPlayer->PlayPulse())
614         PERROR("Succeeded\n");
615       else
616         PERROR("Failed\n");
617     }
618 #endif
619     else if (token == "PLAYURL")
620     {
621       if (++it != tokens.end())
622       {
623         std::string param(*it);
624         while(++it != tokens.end())
625           param.append(" ").append(*it);
626         if (gPlayer->PlayStream(param, ""))
627           PERROR("Succeeded\n");
628         else
629           PERROR("Failed\n");
630       }
631       else
632         PERROR("Error: Missing arguments.\n");
633     }
634     else if (token == "VOLUME")
635     {
636       if (++it != tokens.end())
637       {
638         bool all = true;
639         std::string param(*it);
640         std::string param2;
641         while(++it != tokens.end())
642         {
643           all = false;
644           if ((it + 1) == tokens.end())
645             param2.append(*it);
646           else
647             param.append(" ").append(*it);
648         }
649         if (all)
650           param2.assign(param);
651         SONOS::ZonePtr pl = gPlayer->GetZone();
652         for (SONOS::Zone::iterator ip = pl->begin(); ip != pl->end(); ++ip)
653         {
654           if (all || param == **ip)
655           {
656             uint8_t value = 0;
657             string_to_uint8(param2.c_str(), &value);
658             if (gPlayer->SetVolume((*ip)->GetUUID(), value))
659               PERROR3("%s [%s]: volume %u\n", (*ip)->c_str(), (*ip)->GetUUID().c_str(), value);
660             else
661               PERROR2("%s [%s]: Failed\n", (*ip)->c_str(), (*ip)->GetUUID().c_str());
662           }
663         }
664       }
665       else
666         PERROR("Error: Missing arguments.\n");
667     }
668     else if (token == "SHOWQUEUE")
669     {
670       SONOS::ContentDirectory mycontent(gSonos->GetHost(), gSonos->GetPort());
671       SONOS::ContentList bdir(mycontent, "Q:0");
672       PRINT1("UpdateID  : %u\n", bdir.GetUpdateID());
673       PRINT1("Item count: %u\n", bdir.size());
674       SONOS::ContentList::iterator ic = bdir.begin();
675       int i = 0;
676       while (ic != bdir.end())
677       {
678         PRINT3("%d: [%s] [%s]\n", ++i, (*ic)->GetValue("dc:title").c_str(), (*ic)->GetValue("res").c_str());
679         ++ic;
680       }
681     }
682     else if (token == "PLAYQUEUE")
683     {
684       if (gPlayer->PlayQueue(true))
685         PERROR("Succeeded\n");
686       else
687         PERROR("Failed\n");
688     }
689     else if (token == "PLAYLINEIN")
690     {
691       if (gPlayer->PlayLineIN())
692         PERROR("Succeeded\n");
693       else
694         PERROR("Failed\n");
695     }
696     else if (token == "PLAYDIGITALIN")
697     {
698       if (gPlayer->PlayDigitalIN())
699         PERROR("Succeeded\n");
700       else
701         PERROR("Failed\n");
702     }
703     else if (token == "SHOWFV")
704     {
705       SONOS::ContentDirectory mycontent(gSonos->GetHost(), gSonos->GetPort());
706       SONOS::ContentList bdir(mycontent, "FV:2");
707       PRINT1("UpdateID  : %u\n", bdir.GetUpdateID());
708       PRINT1("Item count: %u\n", bdir.size());
709       SONOS::ContentList::iterator ic = bdir.begin();
710       int i = 0;
711       while (ic != bdir.end())
712       {
713         PRINT3("%d: [%s] [%s]\n", ++i, (*ic)->GetValue("dc:title").c_str(), (*ic)->GetValue("res").c_str());
714         ++ic;
715       }
716     }
717     else if (token == "PLAYFV")
718     {
719       if (++it != tokens.end())
720       {
721         SONOS::ContentDirectory mycontent(gSonos->GetHost(), gSonos->GetPort());
722         SONOS::ContentList bdir(mycontent, "FV:2");
723         SONOS::ContentList::iterator ic = bdir.begin();
724         while (ic != bdir.end())
725         {
726           if ((*ic)->GetValue("res") == (*it))
727           {
728             SONOS::DigitalItemPtr item;
729             if (SONOS::System::ExtractObjectFromFavorite((*ic), item))
730             {
731               if (SONOS::System::CanQueueItem(item))
732               {
733                 PRINT2("Playing item [%s] [%s]\n", (*ic)->GetValue("dc:title").c_str(), (*ic)->GetValue("res").c_str());
734                 if (gPlayer->RemoveAllTracksFromQueue() && gPlayer->PlayQueue(false) && gPlayer->AddURIToQueue(item, 1) && gPlayer->SeekTrack(1) && gPlayer->Play())
735                   PERROR("Succeeded\n");
736                 else
737                   PERROR("Failed\n");
738               }
739               else if (gPlayer->SetCurrentURI(item) && gPlayer->Play())
740                 PERROR("Succeeded\n");
741               else
742                 PERROR("Failed\n");
743             }
744             else
745               PERROR("Failed\n");
746           }
747           ++ic;
748         }
749       }
750       else
751         PERROR("Error: Missing arguments.\n");
752     }
753     else if (token == "SHOWSQ")
754     {
755       SONOS::ContentDirectory mycontent(gSonos->GetHost(), gSonos->GetPort());
756       SONOS::ContentList bdir(mycontent, "SQ:");
757       PRINT1("UpdateID  : %u\n", bdir.GetUpdateID());
758       PRINT1("Item count: %u\n", bdir.size());
759       SONOS::ContentList::iterator ic = bdir.begin();
760       int i = 0;
761       while (ic != bdir.end())
762       {
763         PRINT3("%d: [%s] [%s]\n", ++i, (*ic)->GetValue("dc:title").c_str(), (*ic)->GetValue("res").c_str());
764         ++ic;
765       }
766     }
767     else if (token == "PLAYSQ")
768     {
769       if (++it != tokens.end())
770       {
771         SONOS::ContentDirectory mycontent(gSonos->GetHost(), gSonos->GetPort());
772         SONOS::ContentList bdir(mycontent, "SQ:");
773         SONOS::ContentList::iterator ic = bdir.begin();
774         while (ic != bdir.end())
775         {
776           if ((*ic)->GetValue("res") == (*it))
777           {
778             PRINT2("Playing item [%s] [%s]\n", (*ic)->GetValue("dc:title").c_str(), (*ic)->GetValue("res").c_str());
779             if (gPlayer->RemoveAllTracksFromQueue() && gPlayer->PlayQueue(false) && gPlayer->AddURIToQueue(*ic, 1) && gPlayer->SeekTrack(1) && gPlayer->Play())
780               PERROR("Succeeded\n");
781             else
782               PERROR("Failed\n");
783           }
784           ++ic;
785         }
786       }
787       else
788         PERROR("Error: Missing arguments.\n");
789     }
790     else if (token == "REFRESHSHAREINDEX")
791     {
792       if (gSonos->RefreshShareIndex())
793         PERROR("Succeeded\n");
794       else
795         PERROR("Failed\n");
796     }
797     else
798     {
799       PERROR("Error: Command invalid.\n");
800     }
801   }
802   return true;
803 }
804 
prompt()805 static void prompt() {
806   if (gSonos->IsConnected() && gPlayer)
807     PRINT1("%s >>> ", gPlayer->GetZone()->GetZoneName().c_str());
808   else
809     PRINT(">>> ");
810   FLUSHOUT();
811 }
812 
readInStream()813 static void readInStream()
814 {
815   static int maxlen = 1023;
816   char* buf = new char[maxlen + 1];
817   size_t len = 0;
818   bool run = true;
819 #ifndef __WINDOWS__
820   fd_set fds;
821 #endif
822 
823   prompt();
824 
825   while (run)
826   {
827 #ifndef __WINDOWS__
828     struct timeval tv;
829     tv.tv_sec = 1;
830     tv.tv_usec = 0;
831     FD_ZERO(&fds);
832     FD_SET(STDIN_FILENO, &fds);
833     int r = select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv);
834     if (r > 0 && FD_ISSET(STDIN_FILENO, &fds))
835 #endif
836     {
837       int chr;
838       while (run && (chr = getchar()) != EOF)
839       {
840         if (chr != '\n')
841         {
842           if (len < maxlen)
843             buf[len++] = (char) chr;
844         }
845         else
846         {
847           buf[len] = '\0';
848           if ((run = parseCommand(buf)))
849           {
850             len = 0;
851             prompt();
852           }
853         }
854       }
855     }
856 #ifndef __WINDOWS__
857     else if (r < 0)
858     {
859       if (LASTERROR == ERRNO_INTR)
860         continue;
861       else
862         break;
863     }
864 #endif
865   }
866 
867   delete[] buf;
868 }
869