1 /*
2  *      Copyright (C) 2015-2019 Jean-Luc Barriere
3  *
4  *  This file is part of Noson-App
5  *
6  *  Noson 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 Noson.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 #include "sonos.h"
22 #include "listmodel.h"
23 #include "alarmsmodel.h"
24 
25 #include <noson/requestbroker.h>
26 #include <noson/imageservice.h>
27 #include <noson/filestreamer.h>
28 #ifdef HAVE_PULSEAUDIO
29 #include <noson/pulsestreamer.h>
30 #endif
31 
32 #include <QString>
33 #include <QDebug>
34 
35 #define THREAD_EXPIRY_TIMEOUT  10000
36 #define DEFAULT_MAX_THREAD     16
37 
38 using namespace nosonapp;
39 
40 Sonos::Sonos(QObject* parent)
41 : QObject(parent)
42 , m_library(ManagedContents())
43 , m_shareUpdateID(0)
44 , m_shareIndexInProgess(false)
45 , m_savedQueuesUpdateID(0)
46 , m_system(this, systemEventCB)
47 , m_workerPool()
48 , m_jobCount(0)
49 , m_locale("en_US")
50 {
51   SONOS::System::Debug(2);
52   // Set the local URI of my http listener
53   m_systemLocalURI = QString::fromUtf8(m_system.GetSystemLocalUri().c_str());
54   // Register handlers to process remote request
55   SONOS::RequestBrokerPtr imageService(new SONOS::ImageService());
56   m_system.RegisterRequestBroker(imageService);
57 #ifdef HAVE_PULSEAUDIO
58   m_system.RegisterRequestBroker(SONOS::RequestBrokerPtr(new SONOS::PulseStreamer(imageService.get())));
59 #endif
60   m_system.RegisterRequestBroker(SONOS::RequestBrokerPtr(new SONOS::FileStreamer()));
61 
62   m_workerPool.setExpiryTimeout(THREAD_EXPIRY_TIMEOUT);
63   m_workerPool.setMaxThreadCount(DEFAULT_MAX_THREAD);
64 }
65 
66 Sonos::~Sonos()
67 {
68   {
69     // deregister the remaining contents before destroying this
70     auto left = m_library.Get();
71     for (ManagedContents::iterator it = left->begin(); it != left->end(); ++it)
72       unregisterContent(*left, it->model);
73     left->clear();
74   }
75   m_workerPool.clear();
76 }
77 
78 void Sonos::debug(int debug)
79 {
80   m_system.Debug(debug);
81 }
82 
83 Future* Sonos::tryInit(int debug)
84 {
85   return new Future(new PromiseInit(*this, debug), this);
86 }
87 
88 Future* Sonos::tryRenewSubscriptions()
89 {
90   return new Future(new PromiseRenewSubscriptions(*this), this);
91 }
92 
93 Future* Sonos::tryJoinZones(const QVariantList& zonePayloads, const QVariant& toZonePayload)
94 {
95   return new Future(new PromiseJoinZones(*this, zonePayloads, toZonePayload), this);
96 }
97 
98 Future* Sonos::tryUnjoinZone(const QVariant& zonePayload)
99 {
100   return new Future(new PromiseUnjoinZone(*this, zonePayload), this);
101 }
102 
103 Future* Sonos::tryUnjoinRooms(const QVariantList& roomPayloads)
104 {
105   return new Future(new PromiseUnjoinRooms(*this, roomPayloads), this);
106 }
107 
108 Future* Sonos::tryCreateAlarm(const QVariant& alarmPayload)
109 {
110   return new Future(new PromiseCreateAlarm(*this, alarmPayload), this);
111 }
112 
113 Future* Sonos::tryUpdateAlarm(const QVariant& alarmPayload)
114 {
115   return new Future(new PromiseUpdateAlarm(*this, alarmPayload), this);
116 }
117 
118 Future* Sonos::tryDestroyAlarm(const QString& id)
119 {
120   return new Future(new PromiseDestroyAlarm(*this, id), this);
121 }
122 
123 Future* Sonos::tryRefreshShareIndex()
124 {
125   return new Future(new PromiseRefreshShareIndex(*this), this);
126 }
127 
128 Future* Sonos::tryDestroySavedQueue(const QString& SQid)
129 {
130   return new Future(new PromiseDestroySavedQueue(*this, SQid), this);
131 }
132 
133 Future* Sonos::tryAddItemToFavorites(const QVariant& payload, const QString& description, const QString& artURI)
134 {
135   return new Future(new PromiseAddItemToFavorites(*this, payload, description, artURI), this);
136 }
137 
138 Future* Sonos::tryDestroyFavorite(const QString& FVid)
139 {
140   return new Future(new PromiseDestroyFavorite(*this, FVid), this);
141 }
142 
143 bool Sonos::init(int debug /*= 0*/)
144 {
145   SONOS::System::Debug(2);
146   bool ret = m_system.Discover();
147   SONOS::System::Debug(debug);
148   emit initDone(ret);
149   return ret;
150 }
151 
152 bool Sonos::init(int debug, const QString& url)
153 {
154   SONOS::System::Debug(2);
155   bool ret = m_system.Discover(url.toStdString().c_str());
156   SONOS::System::Debug(debug);
157   emit initDone(ret);
158   return ret;
159 }
160 
161 void Sonos::setLocale(const QString& locale)
162 {
163   m_locale.Store(locale);
164 }
165 
166 QString Sonos::getLocale()
167 {
168   return m_locale.Load();
169 }
170 
171 QString Sonos::getLibVersion()
172 {
173   return QString(LIBVERSION);
174 }
175 
176 void Sonos::addServiceOAuth(const QString& type, const QString& sn, const QString& key, const QString& token, const QString& username)
177 {
178   SONOS::System::AddServiceOAuth(type.toUtf8().constData(), sn.toUtf8().constData(), key.toUtf8().constData(), token.toUtf8().constData(), username.toUtf8().constData());
179 }
180 
181 void Sonos::deleteServiceOAuth(const QString& type, const QString& sn)
182 {
183   SONOS::System::DeleteServiceOAuth(type.toUtf8().constData(), sn.toUtf8().constData());
184 }
185 
186 void Sonos::renewSubscriptions()
187 {
188   m_system.RenewSubscriptions();
189 }
190 
191 QVariantList Sonos::getZones()
192 {
193   ZonesModel model;
194   model.init(this, true);
195   model.resetModel();
196   QVariantList list;
197   for (int r = 0; r < model.rowCount(); ++r)
198     list.append(model.get(r));
199   return list;
200 }
201 
202 bool Sonos::isConnected()
203 {
204   return m_system.IsConnected();
205 }
206 
207 QVariantList Sonos::getZoneRooms(const QString& zoneId)
208 {
209   RoomsModel model;
210   model.load(this, zoneId);
211   QVariantList list;
212   for (int r = 0; r < model.rowCount(); ++r)
213     list.append(model.get(r));
214   return list;
215 }
216 
217 bool Sonos::joinRoom(const QVariant& roomPayload, const QVariant& toZonePayload)
218 {
219   SONOS::ZonePlayerPtr room = roomPayload.value<SONOS::ZonePlayerPtr>();
220   SONOS::ZonePtr zone = toZonePayload.value<SONOS::ZonePtr>();
221   if (room && room->IsValid() && zone && zone->GetCoordinator())
222   {
223     SONOS::Player player(room);
224     return player.JoinToGroup(zone->GetCoordinator()->GetUUID());
225   }
226   return false;
227 }
228 
229 bool Sonos::joinZone(const QVariant& zonePayload, const QVariant& toZonePayload)
230 {
231   SONOS::ZonePtr zone = zonePayload.value<SONOS::ZonePtr>();
232   SONOS::ZonePtr toZone = toZonePayload.value<SONOS::ZonePtr>();
233   if (zone && toZone && toZone->GetCoordinator())
234   {
235     for (std::vector<SONOS::ZonePlayerPtr>::iterator it = zone->begin(); it != zone->end(); ++it)
236     {
237       SONOS::Player player(*it);
238       player.JoinToGroup(toZone->GetCoordinator()->GetUUID());
239     }
240     return true;
241   }
242   return false;
243 
244 }
245 
246 bool Sonos::joinZones(const QVariantList& zonePayloads, const QVariant& toZonePayload)
247 {
248   std::vector<SONOS::ZonePtr> zones;
249   SONOS::ZonePtr toZone = toZonePayload.value<SONOS::ZonePtr>();
250   for (QVariantList::const_iterator it = zonePayloads.begin(); it != zonePayloads.end(); ++it)
251     zones.push_back(it->value<SONOS::ZonePtr>());
252   if (toZone && toZone->GetCoordinator())
253   {
254     for (std::vector<SONOS::ZonePtr>::const_iterator it = zones.begin(); it != zones.end(); ++it)
255     {
256       if ((*it)->GetZoneName() == toZone->GetZoneName())
257         continue;
258       for (std::vector<SONOS::ZonePlayerPtr>::iterator itr = (*it)->begin(); itr != (*it)->end(); ++itr)
259       {
260         SONOS::Player player(*itr);
261         player.JoinToGroup(toZone->GetCoordinator()->GetUUID());
262       }
263     }
264     return true;
265   }
266   return false;
267 }
268 
269 bool Sonos::unjoinRoom(const QVariant& roomPayload)
270 {
271   SONOS::ZonePlayerPtr room = roomPayload.value<SONOS::ZonePlayerPtr>();
272   if (room && room->IsValid())
273   {
274     SONOS::Player player(room);
275     return player.BecomeStandalone();
276   }
277   return false;
278 }
279 
280 bool Sonos::unjoinRooms(const QVariantList& roomPayloads)
281 {
282   for (QVariantList::const_iterator it = roomPayloads.begin(); it != roomPayloads.end(); ++it) {
283     SONOS::ZonePlayerPtr room = it->value<SONOS::ZonePlayerPtr>();
284     if (room && room->IsValid())
285     {
286       SONOS::Player player(room);
287       return player.BecomeStandalone();
288     }
289     else
290       return false;
291   }
292   return true;
293 }
294 
295 bool Sonos::unjoinZone(const QVariant& zonePayload)
296 {
297   SONOS::ZonePtr zone = zonePayload.value<SONOS::ZonePtr>();
298   if (zone)
299   {
300     for (std::vector<SONOS::ZonePlayerPtr>::iterator it = zone->begin(); it != zone->end(); ++it)
301     {
302       SONOS::Player player(*it);
303       player.BecomeStandalone();
304     }
305     return true;
306   }
307   return false;
308 
309 }
310 
311 bool Sonos::createAlarm(const QVariant& alarmPayload)
312 {
313   SONOS::AlarmPtr ptr = alarmPayload.value<SONOS::AlarmPtr>();
314   if (ptr && m_system.CreateAlarm(*ptr))
315   {
316     return true;
317   }
318   return false;
319 }
320 
321 bool Sonos::updateAlarm(const QVariant& alarmPayload)
322 {
323   SONOS::AlarmPtr ptr = alarmPayload.value<SONOS::AlarmPtr>();
324   if (ptr && m_system.UpdateAlarm(*ptr))
325   {
326     return true;
327   }
328   return false;
329 }
330 
331 bool Sonos::destroyAlarm(const QString& id)
332 {
333   return m_system.DestroyAlarm(id.toUtf8().constData());
334 }
335 
336 bool Sonos::refreshShareIndex()
337 {
338   return m_system.RefreshShareIndex();
339 }
340 
341 bool Sonos::destroySavedQueue(const QString& SQid)
342 {
343   return m_system.DestroySavedQueue(SQid.toUtf8().constData());
344 }
345 
346 bool Sonos::addItemToFavorites(const QVariant& payload, const QString& description, const QString& artURI)
347 {
348   return m_system.AddURIToFavorites(payload.value<SONOS::DigitalItemPtr>(), description.toUtf8().constData(), artURI.toUtf8().constData());
349 }
350 
351 bool Sonos::destroyFavorite(const QString& FVid)
352 {
353   return m_system.DestroyFavorite(FVid.toUtf8().constData());
354 }
355 
356 QString Sonos::getObjectIDFromUriMetadata(const QVariant& itemPayload)
357 {
358   SONOS::DigitalItemPtr ptr = itemPayload.value<SONOS::DigitalItemPtr>();
359   return QString::fromUtf8(m_system.GetObjectIDFromUriMetadata(ptr).c_str());
360 }
361 
362 bool Sonos::isItemFromService(const QVariant &itemPayload)
363 {
364   SONOS::DigitalItemPtr ptr = itemPayload.value<SONOS::DigitalItemPtr>();
365   if (ptr && m_system.IsItemFromService(ptr))
366   {
367     return true;
368   }
369   return false;
370 }
371 
372 SONOS::System &Sonos::getSystem()
373 {
374   return m_system;
375 }
376 
377 SONOS::ZonePtr Sonos::findZone(const QString& zoneName)
378 {
379   std::string name = zoneName.toUtf8().constData();
380   SONOS::ZoneList zones = m_system.GetZoneList();
381   if (zones.empty())
382     return SONOS::ZonePtr();
383   // loop in zones
384   for (SONOS::ZoneList::const_iterator it = zones.begin(); it != zones.end(); ++it)
385   {
386     if (name.empty() || name == it->second->GetZoneName())
387       return it->second;
388     // loop in group to search the player with the given name
389     for (std::vector<SONOS::ZonePlayerPtr>::const_iterator itp = it->second->begin(); itp != it->second->end(); ++itp)
390     {
391       if (name == **itp)
392         return it->second;
393     }
394   }
395   return zones.begin()->second;
396 }
397 
398 void Sonos::runLoader()
399 {
400   m_workerPool.start(new ContentLoader<Sonos>(*this));
401 }
402 
403 void Sonos::beforeLoad()
404 {
405   beginJob();
406 }
407 
408 void Sonos::afterLoad()
409 {
410   endJob();
411 }
412 
413 void Sonos::runContentLoader(ListModel<Sonos>* model)
414 {
415   if (model && !model->m_pending)
416   {
417     model->m_pending = true; // decline next request
418     m_workerPool.start(new ContentLoader<Sonos>(*this, model));
419   }
420   else
421     qWarning("%s: request has been declined (%p)", __FUNCTION__, model);
422 }
423 
424 void Sonos::loadContent(ListModel<Sonos>* model)
425 {
426   auto mc = m_library.Get();
427   for (ManagedContents::iterator it = mc->begin(); it != mc->end(); ++it)
428     if (it->model == model)
429     {
430       qDebug("%s: %p (%s)", __FUNCTION__, model, model->m_root.toUtf8().constData());
431       emit loadingStarted();
432       model->m_pending = false; // accept add next request in queue
433       model->loadData();
434       emit loadingFinished();
435       break;
436     }
437 }
438 
439 void Sonos::loadAllContent()
440 {
441   QList<ListModel<Sonos>*> left;
442   {
443     auto mc = m_library.Get();
444     for (ManagedContents::iterator it = mc->begin(); it != mc->end(); ++it)
445       if (it->model->m_dataState == DataStatus::DataNotFound)
446         left.push_back(it->model);
447   }
448   emit loadingStarted();
449   if (!left.empty())
450   {
451     while (!left.isEmpty())
452     {
453       ListModel<Sonos>* model = left.front();
454       model->loadData();
455       left.pop_front();
456     }
457   }
458   emit loadingFinished();
459 }
460 
461 void Sonos::runContentLoaderForContext(ListModel<Sonos>* model, int id)
462 {
463   if (model && !model->m_pending)
464   {
465     model->m_pending = true; // decline next request
466     m_workerPool.start(new ContentForContextLoader<Sonos>(*this, model, id));
467   }
468   else
469     qWarning("%s: request id %d has been declined (%p)", __FUNCTION__, id, model);
470 }
471 
472 void Sonos::loadContentForContext(ListModel<Sonos>* model, int id)
473 {
474   model->m_pending = false; // accept add next request in queue
475   model->loadDataForContext(id);
476 }
477 
478 const char* Sonos::getHost() const
479 {
480   return m_system.GetHost().c_str();
481 }
482 
483 unsigned Sonos::getPort() const
484 {
485   return m_system.GetPort();
486 }
487 
488 QString Sonos::getBaseUrl() const
489 {
490   QString port;
491   port.setNum(m_system.GetPort());
492   QString url = "http://";
493   url.append(m_system.GetHost().c_str()).append(":").append(port);
494   return url;
495 }
496 
497 void Sonos::registerContent(ListModel<Sonos>* model, const QString& root)
498 {
499   if (model)
500   {
501     qDebug("%s: %p (%s)", __FUNCTION__, model, root.toUtf8().constData());
502     auto mc = m_library.Get();
503     for (ManagedContents::iterator it = mc->begin(); it != mc->end(); ++it)
504     {
505       if (it->model == model)
506       {
507         it->root = root;
508         return;
509       }
510     }
511     mc->append(RegisteredContent<Sonos>(model, root));
512   }
513 }
514 
515 void Sonos::unregisterContent(ListModel<Sonos>* model)
516 {
517   auto mc = m_library.Get();
518   unregisterContent(*mc, model);
519 }
520 
521 bool Sonos::startJob(QRunnable* worker)
522 {
523   return m_workerPool.tryStart(worker);
524 }
525 
526 void Sonos::beginJob()
527 {
528   m_jobCount.Add(1);
529   emit jobCountChanged();
530 }
531 
532 void Sonos::endJob()
533 {
534   m_jobCount.Add(-1);
535   emit jobCountChanged();
536 }
537 
538 void Sonos::systemEventCB(void *handle)
539 {
540   Sonos* sonos = static_cast<Sonos*>(handle);
541   Q_ASSERT(sonos);
542   // Read last event flags
543   unsigned char events = sonos->getSystem().LastEvents();
544 
545   if ((events & SONOS::SVCEvent_ZGTopologyChanged))
546     emit sonos->topologyChanged();
547   if ((events & SONOS::SVCEvent_AlarmClockChanged))
548     emit sonos->alarmClockChanged();
549   if ((events & SONOS::SVCEvent_ContentDirectoryChanged))
550   {
551     auto cl = sonos->m_library.Get();
552     SONOS::ContentProperty prop = sonos->getSystem().GetContentProperty();
553     for (std::vector<std::pair<std::string, unsigned> >::const_iterator uit = prop.ContainerUpdateIDs.begin(); uit != prop.ContainerUpdateIDs.end(); ++uit)
554     {
555       qDebug("%s: container [%s] has being updated to %u", __FUNCTION__, uit->first.c_str(), uit->second);
556 
557       bool shareUpdated = false;
558       bool savedQueuesUpdated = false;
559 
560       // Reload musical index on any update of shares
561       if (uit->first == "S:" && uit->second != sonos->m_shareUpdateID)
562       {
563         shareUpdated = true;
564         sonos->m_shareUpdateID = uit->second; // Track current updateID
565       }
566       // Reload saved queues on any update
567       else if (uit->first == "SQ:" && uit->second != sonos->m_savedQueuesUpdateID)
568       {
569         savedQueuesUpdated = true;
570         sonos->m_savedQueuesUpdateID = uit->second; // Track current updateID
571       }
572 
573       for (ManagedContents::iterator it = cl->begin(); it != cl->end(); ++it)
574       {
575         // find the base of the model from its root
576         QString _base;
577         int slash = it->model->m_root.indexOf("/");
578         if (slash < 0)
579           _base.append(it->model->m_root);
580         else
581           _base.append(it->model->m_root.left(slash));
582 
583         // need update ?
584         bool _update = false;
585         // same base
586         if (it->model->m_updateID != uit->second && _base == uit->first.c_str())
587           _update = true;
588         // about shares
589         else if (shareUpdated && _base.startsWith(QString::fromUtf8("A:")))
590           _update = true;
591         else if (savedQueuesUpdated && _base.startsWith(QString::fromUtf8("SQ:")))
592           _update = true;
593 
594         if (_update)
595           it->model->handleDataUpdate();
596       }
597     }
598     // Signal share index events
599     if (prop.ShareIndexInProgress != sonos->m_shareIndexInProgess)
600     {
601       if (prop.ShareIndexInProgress)
602         emit sonos->shareIndexInProgress();
603       else
604         emit sonos->shareIndexFinished();
605       sonos->m_shareIndexInProgess = prop.ShareIndexInProgress;
606     }
607   }
608 }
609 
610 void Sonos::unregisterContent(ManagedContents& mc, ListModel<Sonos> *model)
611 {
612   if (model)
613   {
614     QList<ManagedContents::iterator> left;
615     for (ManagedContents::iterator it = mc.begin(); it != mc.end(); ++it)
616       if (it->model == model)
617       {
618         LockGuard<QRecursiveMutex> g(it->model->m_lock);
619         qDebug("%s: %p (%s)", __FUNCTION__, it->model, it->model->m_root.toUtf8().constData());
620         it->model->m_provider = nullptr;
621         left.push_back(it);
622       }
623     for (QList<ManagedContents::iterator>::iterator itl = left.begin(); itl != left.end(); ++itl)
624       mc.erase(*itl);
625   }
626 }
627 
628 ///////////////////////////////////////////////////////////////////////////////
629 ///
630 /// About promises
631 
632 void Sonos::PromiseInit::run()
633 {
634   bool r = m_sonos.init(m_debug);
635   setResult(QVariant(r));
636 }
637 
638 void Sonos::PromiseRenewSubscriptions::run()
639 {
640   m_sonos.renewSubscriptions();
641   setResult(QVariant(true));
642 }
643 
644 void Sonos::PromiseJoinZones::run()
645 {
646   bool r = m_sonos.joinZones(m_zonePayloads, m_toZonePayload);
647   setResult(QVariant(r));
648 }
649 
650 void Sonos::PromiseUnjoinZone::run()
651 {
652   bool r = m_sonos.unjoinZone(m_zonePayload);
653   setResult(QVariant(r));
654 }
655 
656 void Sonos::PromiseUnjoinRooms::run()
657 {
658   bool r = m_sonos.unjoinRooms(m_roomPayloads);
659   setResult(QVariant(r));
660 }
661 
662 void Sonos::PromiseCreateAlarm::run()
663 {
664   bool r = m_sonos.createAlarm(m_alarmPayload);
665   setResult(QVariant(r));
666 }
667 
668 void Sonos::PromiseUpdateAlarm::run()
669 {
670   bool r = m_sonos.updateAlarm(m_alarmPayload);
671   setResult(QVariant(r));
672 }
673 
674 void Sonos::PromiseDestroyAlarm::run()
675 {
676   bool r = m_sonos.destroyAlarm(m_id);
677   setResult(QVariant(r));
678 }
679 
680 void Sonos::PromiseRefreshShareIndex::run()
681 {
682   bool r = m_sonos.refreshShareIndex();
683   setResult(QVariant(r));
684 }
685 
686 void Sonos::PromiseDestroySavedQueue::run()
687 {
688   bool r = m_sonos.destroySavedQueue(m_SQid);
689   setResult(QVariant(r));
690 }
691 
692 void Sonos::PromiseAddItemToFavorites::run()
693 {
694   bool r = m_sonos.addItemToFavorites(m_payload, m_description, m_artURI);
695   setResult(QVariant(r));
696 }
697 
698 void Sonos::PromiseDestroyFavorite::run()
699 {
700   bool r = m_sonos.destroyFavorite(m_FVid);
701   setResult(QVariant(r));
702 }
703