1 //////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (c) 2004-2021 musikcube team
4 //
5 // All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions are met:
9 //
10 //    * Redistributions of source code must retain the above copyright notice,
11 //      this list of conditions and the following disclaimer.
12 //
13 //    * Redistributions in binary form must reproduce the above copyright
14 //      notice, this list of conditions and the following disclaimer in the
15 //      documentation and/or other materials provided with the distribution.
16 //
17 //    * Neither the name of the author nor the names of other contributors may
18 //      be used to endorse or promote products derived from this software
19 //      without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
25 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
32 //
33 //////////////////////////////////////////////////////////////////////////////
34 
35 #include "WebSocketServer.h"
36 #include "Constants.h"
37 
38 #include <iostream>
39 
40 #include <musikcore/sdk/constants.h>
41 
42 using websocketpp::lib::placeholders::_1;
43 using websocketpp::lib::placeholders::_2;
44 using websocketpp::lib::bind;
45 
46 using namespace nlohmann;
47 using namespace musik::core::sdk;
48 
49 static int nextId = 0;
50 
51 /* UTILITY METHODS */
52 
53 namespace str {
54     template<typename... Args>
format(const std::string & format,Args...args)55     static std::string format(const std::string& format, Args ... args) {
56         /* https://stackoverflow.com/a/26221725 */
57         size_t size = std::snprintf(nullptr, 0, format.c_str(), args ...) + 1; /* extra space for '\0' */
58         std::unique_ptr<char[]> buf(new char[size]);
59         std::snprintf(buf.get(), size, format.c_str(), args ...);
60         return std::string(buf.get(), buf.get() + size - 1); /* omit the '\0' */
61     }
62 }
63 
nextMessageId()64 static std::string nextMessageId() {
65     return str::format("musikcube-server-%d", ++nextId);
66 }
67 
jsonToStringArray(const json & jsonArray)68 static std::shared_ptr<char*> jsonToStringArray(const json& jsonArray) {
69     char** result = nullptr;
70     size_t count = 0;
71 
72     if (jsonArray.is_array()) {
73         count = jsonArray.size();
74         result = (char**) malloc(count * sizeof(char*));
75         for (size_t i = 0; i < count; i++) {
76             std::string str = jsonArray[i];
77             size_t size = str.size();
78             result[i] = (char*) malloc(size + 1);
79             strncpy(result[i], str.c_str(), size);
80             result[i][size] = 0;
81         }
82     }
83 
84     return std::shared_ptr<char*>(result, [count](char** result) {
85         if (result) {
86             for (size_t i = 0; i < count; i++) {
87                 free(result[i]);
88             }
89             free(result);
90         }
91     });
92 }
93 
94 template <typename T>
jsonToIntArray(json & arr)95 static std::shared_ptr<T> jsonToIntArray(json& arr) {
96     size_t count = arr.is_array() ? arr.size() : 0;
97 
98     T* idArray = new T[count];
99     if (count > 0) {
100         std::copy(arr.begin(), arr.end(), idArray);
101     }
102 
103     return std::shared_ptr<T>(idArray, [count](T* result) {
104         delete[] result;
105     });
106 }
107 
jsonToPredicateList(json & arr)108 static std::shared_ptr<IValue*> jsonToPredicateList(json& arr) {
109     size_t count = arr.is_array() ? arr.size() : 0;
110     IValue** valueArray = new IValue*[count];
111 
112     for (size_t i = 0; i < count; i++) {
113         valueArray[i] = CreateValue(
114             arr[i]["category"],
115             arr[i]["id"],
116             "category");
117     }
118 
119     return std::shared_ptr<IValue*>(valueArray, [count](IValue** del) {
120         for (size_t i = 0; i < count; i++) {
121             del[i]->Release();
122         }
123         delete[] del;
124     });
125 }
126 
getEnvironment(Context & context)127 static json getEnvironment(Context& context) {
128     return {
129         { prefs::http_server_enabled, context.prefs->GetBool(prefs::http_server_enabled.c_str()) },
130         { prefs::http_server_port, context.prefs->GetInt(prefs::http_server_port.c_str()) },
131         { key::sdk_version, musik::core::sdk::SdkVersion },
132         { key::app_version, context.environment->GetAppVersion() },
133         { key::api_version, ApiVersion }
134     };
135 }
136 
137 /* IMPLEMENTATION */
138 
WebSocketServer(Context & context)139 WebSocketServer::WebSocketServer(Context& context)
140 : context(context)
141 , running(false) {
142 
143 }
144 
~WebSocketServer()145 WebSocketServer::~WebSocketServer() {
146     this->Stop();
147 }
148 
Wait()149 void WebSocketServer::Wait() {
150     std::unique_lock<std::mutex> lock(this->exitMutex);
151     while (this->running) {
152         this->exitCondition.wait(lock);
153     }
154 }
155 
Start()156 bool WebSocketServer::Start() {
157     this->Stop();
158     this->running = true;
159     this->thread.reset(new std::thread(std::bind(&WebSocketServer::ThreadProc, this)));
160 
161     return true;
162 }
163 
ThreadProc()164 void WebSocketServer::ThreadProc() {
165     try {
166         wss.reset(new server());
167 
168         if (context.prefs->GetBool("debug")) {
169             wss->get_alog().set_ostream(&std::cerr);
170             wss->get_elog().set_ostream(&std::cerr);
171             wss->set_access_channels(websocketpp::log::alevel::all);
172             wss->clear_access_channels(websocketpp::log::alevel::frame_payload);
173         }
174         else {
175             wss->set_access_channels(websocketpp::log::alevel::none);
176             wss->clear_access_channels(websocketpp::log::alevel::none);
177         }
178 
179         using namespace websocketpp::lib::asio::ip;
180 
181         const int port = context.prefs->GetInt(
182             prefs::websocket_server_port.c_str(), defaults::websocket_server_port);
183 
184         const bool ipv6 = context.prefs->GetBool(
185             prefs::use_ipv6.c_str(), defaults::use_ipv6);
186 
187         wss->init_asio();
188         wss->set_reuse_addr(true);
189         wss->set_message_handler(std::bind(&WebSocketServer::OnMessage, this, wss.get(), ::_1, ::_2));
190         wss->set_open_handler(std::bind(&WebSocketServer::OnOpen, this, ::_1));
191         wss->set_close_handler(std::bind(&WebSocketServer::OnClose, this, ::_1));
192         wss->listen(ipv6 ? tcp::v6() : tcp::v4(), port);
193         wss->start_accept();
194 
195         wss->run();
196     }
197     catch (websocketpp::exception const & e) {
198         std::cerr << e.what() << std::endl;
199     }
200     catch (std::exception& e) {
201         std::cerr << "ThreadProc failed: " << e.what() << std::endl;
202     }
203     catch (...) {
204         std::cerr << "unknown exception" << std::endl;
205     }
206 
207     this->wss.reset();
208     this->running = false;
209     this->snapshots.Reset();
210 
211     this->exitCondition.notify_all();
212 }
213 
Stop()214 bool WebSocketServer::Stop() {
215     if (this->thread) {
216         if (this->wss) {
217             wss->stop();
218         }
219 
220         this->thread->join();
221         this->thread.reset();
222     }
223 
224     this->running = false;
225     this->exitCondition.notify_all();
226 
227     return true;
228 }
229 
OnTrackChanged(ITrack * track)230 void WebSocketServer::OnTrackChanged(ITrack* track) {
231     this->BroadcastPlaybackOverview();
232 }
233 
OnPlaybackStateChanged(PlaybackState state)234 void WebSocketServer::OnPlaybackStateChanged(PlaybackState state) {
235     this->BroadcastPlaybackOverview();
236 }
237 
OnPlaybackTimeChanged(double time)238 void WebSocketServer::OnPlaybackTimeChanged(double time) {
239     this->BroadcastPlaybackOverview();
240 }
241 
OnVolumeChanged(double volume)242 void WebSocketServer::OnVolumeChanged(double volume) {
243     this->BroadcastPlaybackOverview();
244 }
245 
OnModeChanged(RepeatMode repeatMode,bool shuffled)246 void WebSocketServer::OnModeChanged(RepeatMode repeatMode, bool shuffled) {
247     this->BroadcastPlaybackOverview();
248 }
249 
OnPlayQueueChanged()250 void WebSocketServer::OnPlayQueueChanged() {
251     this->BroadcastPlayQueueChanged();
252 }
253 
HandleAuthentication(connection_hdl connection,json & request)254 void WebSocketServer::HandleAuthentication(connection_hdl connection, json& request) {
255     std::string name = request[message::name];
256 
257     if (name == request::authenticate) {
258         std::string sent = request[message::options][key::password];
259 
260         std::string actual = GetPreferenceString(
261             context.prefs, key::password, defaults::password);
262 
263         if (sent == actual) {
264             this->connections[connection] = true; /* mark as authed */
265 
266             this->RespondWithOptions(
267                 connection, request, json({
268                     { key::authenticated, true },
269                     { key::environment, getEnvironment(context) }
270                 }));
271 
272             return;
273         }
274     }
275 
276     this->wss->close(
277         connection,
278         websocketpp::close::status::policy_violation,
279         value::unauthenticated);
280 }
281 
HandleRequest(connection_hdl connection,json & request)282 void WebSocketServer::HandleRequest(connection_hdl connection, json& request) {
283     if (this->connections[connection] == false) {
284         this->HandleAuthentication(connection, request);
285         return;
286     }
287 
288     std::string name = request[message::name];
289     std::string id = request[message::id];
290 
291     auto deviceId = request.value(message::device_id, "");
292     if (deviceId.size() == 0) {
293         deviceId = "default_device_id";
294         request[message::device_id] = deviceId;
295     }
296 
297     if (!name.size() || !id.size()) {
298         RespondWithInvalidRequest(connection, name, id);
299     }
300     else if (context.playback) {
301         if (request.find(message::options) == request.end()) {
302             request[message::options] = {};
303         }
304 
305         const json& options = request[message::options];
306 
307         if (name == request::ping) {
308             this->RespondWithSuccess(connection, request);
309             return;
310         }
311         if (name == request::send_raw_query) {
312             this->RespondWithSendRawQuery(connection, request);
313             return;
314         }
315         if (name == request::pause_or_resume) {
316             context.playback->PauseOrResume();
317             this->RespondWithSuccess(connection, request);
318             return;
319         }
320         else if (name == request::stop) {
321             context.playback->Stop();
322             this->RespondWithSuccess(connection, request);
323             return;
324         }
325         else if (name == request::previous) {
326             if (context.playback->Previous()) {
327                 this->RespondWithSuccess(connection, request);
328             }
329             else {
330                 this->RespondWithFailure(connection, request);
331             }
332             return;
333         }
334         else if (name == request::next) {
335             if (context.playback->Next()) {
336                 this->RespondWithSuccess(connection, request);
337             }
338             else {
339                 this->RespondWithFailure(connection, request);
340             }
341             return;
342         }
343         else if (name == request::play_at_index) {
344             if (options.find(key::index) != options.end()) {
345                 context.playback->Play(options[key::index]);
346                 this->RespondWithSuccess(connection, request);
347             }
348             else {
349                 this->RespondWithFailure(connection, request);
350             }
351             return;
352         }
353         else if (name == request::toggle_shuffle) {
354             context.playback->ToggleShuffle();
355             this->RespondWithSuccess(connection, request);
356             return;
357         }
358         else if (name == request::toggle_repeat) {
359             context.playback->ToggleRepeatMode();
360             this->RespondWithSuccess(connection, request);
361             return;
362         }
363         else if (name == request::toggle_mute) {
364             context.playback->ToggleMute();
365             this->RespondWithSuccess(connection, request);
366             return;
367         }
368         else if (name == request::set_volume) {
369             this->RespondWithSetVolume(connection, request);
370             return;
371         }
372         else if (name == request::seek_to) {
373             if (options.find(key::position) != options.end()) {
374                 context.playback->SetPosition(options[key::position]);
375                 this->RespondWithSuccess(connection, request);
376             }
377             else {
378                 this->RespondWithFailure(connection, request);
379             }
380             return;
381         }
382         else if (name == request::seek_relative) {
383             double delta = options.value(key::delta, 0.0f);
384             if (delta != 0.0f) {
385                 context.playback->SetPosition(context.playback->GetPosition() + delta);
386                 this->RespondWithSuccess(connection, request);
387             }
388             else {
389                 this->RespondWithFailure(connection, request);
390             }
391             return;
392         }
393         else if (name == request::get_playback_overview) {
394             RespondWithPlaybackOverview(connection, request);
395             return;
396         }
397         else if (name == request::list_categories) {
398             RespondWithListCategories(connection, request);
399             return;
400         }
401         else if (name == request::query_category) {
402             RespondWithQueryCategory(connection, request);
403             return;
404         }
405         else if (name == request::query_tracks) {
406             RespondWithQueryTracks(connection, request);
407             return;
408         }
409         else if (name == request::query_tracks_by_category) {
410             RespondWithQueryTracksByCategory(connection, request);
411             return;
412         }
413         else if (name == request::query_tracks_by_external_ids) {
414             RespondWithQueryTracksByExternalIds(connection, request);
415             return;
416         }
417         else if (name == request::query_albums) {
418             RespondWithQueryAlbums(connection, request);
419             return;
420         }
421         else if (name == request::query_play_queue_tracks) {
422             RespondWithPlayQueueTracks(connection, request);
423             return;
424         }
425         else if (name == request::play_all_tracks) {
426             this->RespondWithPlayAllTracks(connection, request);
427             return;
428         }
429         else if (name == request::play_snapshot_tracks) {
430             this->RespondWithPlaySnapshotTracks(connection, request);
431             return;
432         }
433         else if (name == request::play_tracks) {
434             this->RespondWithPlayTracks(connection, request);
435             return;
436         }
437         else if (name == request::play_tracks_by_category) {
438             this->RespondWithPlayTracksByCategory(connection, request);
439             return;
440         }
441         else if (name == request::get_environment) {
442             this->RespondWithEnvironment(connection, request);
443             return;
444         }
445         else if (name == request::get_current_time) {
446             this->RespondWithCurrentTime(connection, request);
447             return;
448         }
449         else if (name == request::save_playlist) {
450             this->RespondWithSavePlaylist(connection, request);
451             return;
452         }
453         else if (name == request::rename_playlist) {
454             this->RespondWithRenamePlaylist(connection, request);
455             return;
456         }
457         else if (name == request::delete_playlist) {
458             this->RespondWithDeletePlaylist(connection, request);
459             return;
460         }
461         else if (name == request::append_to_playlist) {
462             this->RespondWithAppendToPlaylist(connection, request);
463             return;
464         }
465         else if (name == request::remove_tracks_from_playlist) {
466             this->RespondWithRemoveTracksFromPlaylist(connection, request);
467             return;
468         }
469         else if (name == request::run_indexer) {
470             this->RespondWithRunIndexer(connection, request);
471             return;
472         }
473         else if (name == request::list_output_drivers) {
474             this->RespondWithListOutputDrivers(connection, request);
475             return;
476         }
477         else if (name == request::set_default_output_driver) {
478             this->RespondWithSetDefaultOutputDriver(connection, request);
479             return;
480         }
481         else if (name == request::get_gain_settings) {
482             this->RespondWithGetGainSettings(connection, request);
483             return;
484         }
485         else if (name == request::set_gain_settings) {
486             this->RespondWithSetGainSettings(connection, request);
487             return;
488         }
489         else if (name == request::get_equalizer_settings) {
490             this->RespondWithGetEqualizerSettings(connection, request);
491             return;
492         }
493         else if (name == request::set_equalizer_settings) {
494             this->RespondWithSetEqualizerSettings(connection, request);
495             return;
496         }
497         else if (name == request::get_transport_type) {
498             this->RespondWithGetTransportType(connection, request);
499             return;
500         }
501         else if (name == request::set_transport_type) {
502             this->RespondWithSetTransportType(connection, request);
503             return;
504         }
505         else if (name == request::snapshot_play_queue) {
506             this->RespondWithSnapshotPlayQueue(connection, request);
507             return;
508         }
509         else if (name == request::invalidate_play_queue_snapshot) {
510             this->snapshots.Remove(deviceId);
511             this->RespondWithSuccess(connection, request);
512             return;
513         }
514     }
515 
516     this->RespondWithInvalidRequest(connection, name, id);
517 }
518 
Broadcast(const std::string & name,json & options)519 void WebSocketServer::Broadcast(const std::string& name, json& options) {
520     json msg;
521     msg[message::name] = name;
522     msg[message::type] = type::broadcast;
523     msg[message::id] = nextMessageId();
524     msg[message::options] = options;
525 
526     std::string str = msg.dump();
527 
528     auto rl = connectionLock.Read();
529     try {
530         if (wss) {
531             for (const auto &keyValue : this->connections) {
532                 wss->send(keyValue.first, str.c_str(), websocketpp::frame::opcode::text);
533             }
534         }
535     }
536     catch (...) {
537         std::cerr << "broadcast failed (stale connection?)\n";
538     }
539 }
540 
RespondWithOptions(connection_hdl connection,json & request,json & options)541 void WebSocketServer::RespondWithOptions(connection_hdl connection, json& request, json& options) {
542     json response = {
543         { message::name, request[message::name] },
544         { message::type, type::response },
545         { message::id, request[message::id] },
546         { message::options, options }
547     };
548 
549     wss->send(connection, response.dump().c_str(), websocketpp::frame::opcode::text);
550 }
551 
RespondWithOptions(connection_hdl connection,json & request,json && options)552 void WebSocketServer::RespondWithOptions(connection_hdl connection, json& request, json&& options) {
553     json response = {
554         { message::name, request[message::name] },
555         { message::type, type::response },
556         { message::id, request[message::id] },
557         { message::options, options }
558     };
559 
560     wss->send(connection, response.dump().c_str(), websocketpp::frame::opcode::text);
561 }
562 
RespondWithInvalidRequest(connection_hdl connection,const std::string & name,const std::string & id)563 void WebSocketServer::RespondWithInvalidRequest(connection_hdl connection, const std::string& name, const std::string& id)
564 {
565     json error = {
566         { message::name, name },
567         { message::id, id },
568         { message::type, type::response },
569         { message::options,{{ key::error, value::invalid }} }
570     };
571 
572     wss->send(connection, error.dump().c_str(), websocketpp::frame::opcode::text);
573 }
574 
RespondWithSuccess(connection_hdl connection,json & request)575 void WebSocketServer::RespondWithSuccess(connection_hdl connection, json& request) {
576     std::string name = request[message::name];
577     std::string id = request[message::id];
578     this->RespondWithSuccess(connection, name, id);
579 }
580 
RespondWithSuccess(connection_hdl connection,const std::string & name,const std::string & id)581 void WebSocketServer::RespondWithSuccess(connection_hdl connection, const std::string& name, const std::string& id)
582 {
583     json success = {
584         { message::name, name },
585         { message::id, id },
586         { message::type, type::response },
587         { message::options, {{ key::success, true }} }
588     };
589 
590     wss->send(connection, success.dump().c_str(), websocketpp::frame::opcode::text);
591 }
592 
RespondWithFailure(connection_hdl connection,json & request)593 void WebSocketServer::RespondWithFailure(connection_hdl connection, json& request) {
594     json error = {
595         { message::name, request[message::name] },
596         { message::id, request[message::id] },
597         { message::type, type::response },
598         { message::options, {{ key::success, false }} }
599     };
600 
601     wss->send(connection, error.dump().c_str(), websocketpp::frame::opcode::text);
602 }
603 
RespondWithSendRawQuery(connection_hdl connection,json & request)604 void WebSocketServer::RespondWithSendRawQuery(connection_hdl connection, json& request) {
605     json& options = request[message::options];
606     std::string data = options.value(key::raw_query_data, "");
607     PluginAllocator<WebSocketServer> allocator;
608     bool responded = false;
609     char* responseData = nullptr;
610     int responseSize = 0;
611     if (context.metadataProxy->SendRawQuery(data.c_str(), allocator, &responseData, &responseSize)) {
612         if (responseSize) {
613             this->RespondWithOptions(connection, request, { { key::raw_query_data, responseData } });
614             responded = true;
615         }
616         allocator.Free((void*) responseData);
617     }
618     if (!responded) {
619         this->RespondWithFailure(connection, request);
620     }
621 }
622 
RespondWithSetVolume(connection_hdl connection,json & request)623 void WebSocketServer::RespondWithSetVolume(connection_hdl connection, json& request) {
624     json& options = request[message::options];
625     std::string relative = options.value(key::relative, "");
626 
627     if (relative == value::up) {
628         double delta = round(context.playback->GetVolume() * 100.0) >= 10.0 ? 0.05 : 0.01;
629         context.playback->SetVolume(context.playback->GetVolume() + delta);
630     }
631     else if (relative == value::down) {
632         double delta = round(context.playback->GetVolume() * 100.0) > 10.0 ? 0.05 : 0.01;
633         context.playback->SetVolume(context.playback->GetVolume() - delta);
634     }
635     else if (relative == value::delta) {
636         float delta = options[key::volume];
637         context.playback->SetVolume(context.playback->GetVolume() + delta);
638     }
639     else {
640         context.playback->SetVolume(options[key::volume]);
641     }
642 
643     this->RespondWithSuccess(connection, request);
644 }
645 
RespondWithPlaybackOverview(connection_hdl connection,json & request)646 void WebSocketServer::RespondWithPlaybackOverview(connection_hdl connection, json& request) {
647     json options;
648     this->BuildPlaybackOverview(options);
649     this->RespondWithOptions(connection, request, options);
650 }
651 
RespondWithTracks(connection_hdl connection,json & request,ITrackList * tracks,int limit,int offset)652 bool WebSocketServer::RespondWithTracks(
653     connection_hdl connection,
654     json& request,
655     ITrackList* tracks,
656     int limit,
657     int offset)
658 {
659     json& options = request[message::options];
660     bool countOnly = options.value(key::count_only, false);
661     bool idsOnly = options.value(key::ids_only, false);
662 
663     if (tracks) {
664         if (countOnly) {
665             this->RespondWithOptions(connection, request, {
666                 { key::data, json::array() },
667                 { key::count, tracks->Count() }
668             });
669 
670             tracks->Release();
671 
672             return true;
673         }
674         else {
675             json data = json::array();
676 
677             ITrack* track = nullptr;
678             for (size_t i = 0; i < tracks->Count(); i++) {
679                 track = tracks->GetTrack(i);
680 
681                 if (idsOnly) {
682                     data.push_back(GetMetadataString(track, key::external_id));
683                 }
684                 else {
685                     data.push_back(this->ReadTrackMetadata(track));
686                 }
687 
688                 track->Release();
689             }
690 
691             tracks->Release();
692 
693             this->RespondWithOptions(connection, request, {
694                 { key::data, data },
695                 { key::count, data.size() },
696                 { key::limit, std::max(0, limit) },
697                 { key::offset, offset },
698             });
699 
700             return true;
701         }
702     }
703 
704     return false;
705 }
706 
GetLimitAndOffset(json & options,int & limit,int & offset)707 void WebSocketServer::GetLimitAndOffset(json& options, int& limit, int& offset) {
708     int optionsLimit = options.value(key::limit, -1);
709     int optionsOffset = options.value(key::offset, 0);
710     if (optionsLimit != -1 && optionsOffset >= 0) {
711         limit = optionsLimit;
712         offset = optionsOffset;
713     }
714 }
715 
QueryTracks(json & request,int & limit,int & offset)716 ITrackList* WebSocketServer::QueryTracks(json& request, int& limit, int& offset) {
717     if (request.find(message::options) != request.end()) {
718         json& options = request[message::options];
719         std::string filter = options.value(key::filter, "");
720         this->GetLimitAndOffset(options, limit, offset);
721         return context.metadataProxy->QueryTracks(filter.c_str(), limit, offset);
722     }
723     return nullptr;
724 }
725 
RespondWithQueryTracks(connection_hdl connection,json & request)726 void WebSocketServer::RespondWithQueryTracks(connection_hdl connection, json& request) {
727     if (request.find(message::options) != request.end()) {
728         int limit = -1, offset = 0;
729         ITrackList* tracks = this->QueryTracks(request, limit, offset);
730         if (this->RespondWithTracks(connection, request, tracks, limit, offset)) {
731             return;
732         }
733     }
734 
735     this->RespondWithInvalidRequest(connection, request[message::name], value::invalid);
736 }
737 
RespondWithQueryTracksByExternalIds(connection_hdl connection,json & request)738 void WebSocketServer::RespondWithQueryTracksByExternalIds(connection_hdl connection, json& request) {
739     auto& options = request[message::options];
740 
741     if (options.find(key::external_ids) != options.end()) {
742         json& externalIds = options[key::external_ids];
743         if (externalIds.is_array()) {
744             auto externalIdArray = jsonToStringArray(externalIds);
745             ITrackList* trackList = context.metadataProxy
746                 ->QueryTracksByExternalId(
747                     (const char**) externalIdArray.get(),
748                     externalIds.size());
749 
750             if (trackList) {
751                 json tracks = { };
752 
753                 ITrack* track;
754                 std::string externalId;
755                 for (size_t i = 0; i < trackList->Count(); i++) {
756                     track = trackList->GetTrack(i);
757                     externalId = GetMetadataString(track, track::ExternalId);
758                     tracks[externalId] = this->ReadTrackMetadata(track);
759                     track->Release();
760                 }
761 
762                 trackList->Release();
763                 json options = { { key::data, tracks } };
764 
765                 this->RespondWithOptions(connection, request, options);
766                 return;
767             }
768         }
769     }
770 
771     this->RespondWithInvalidRequest(connection, request[message::name], value::invalid);
772 }
773 
774 
RespondWithPlayQueueTracks(connection_hdl connection,json & request)775 void WebSocketServer::RespondWithPlayQueueTracks(connection_hdl connection, json& request) {
776     /* for the output */
777     bool countOnly = false;
778     int limit = -1;
779     int offset = 0;
780 
781     /* note: the user can query the "live" (i.e. current) play queue, or a
782     a previously "snapshotted" playqueue. the former is generally used for
783     remote playback, the latter is used for transfering context from remote
784     to streaming. */
785     std::string type = value::live;
786 
787     if (request.find(message::options) != request.end()) {
788         json& options = request[message::options];
789         countOnly = options.value(key::count_only, false);
790         type = options.value(key::type, type);
791 
792         if (!countOnly) {
793             this->GetLimitAndOffset(options, limit, offset);
794         }
795     }
796 
797     if (countOnly) {
798         size_t count = context.playback->Count();
799 
800         if (type == value::snapshot) {
801             auto snapshot = snapshots.Get(request[message::device_id]);
802             count = snapshot ? snapshot->Count() : 0;
803         }
804 
805         this->RespondWithOptions(connection, request, {
806             { key::data, json::array() },
807             { key::count, count }
808         });
809     }
810     else {
811         bool idsOnly = request[message::options].value(key::ids_only, false);
812 
813         /* now add the tracks to the output. they will be Release()'d automatically
814         as soon as this scope ends. */
815         json data = json::array();
816 
817         if (type == value::live) {
818             /* edit the playlist so it can be changed while we're getting the tracks
819             out of it. only applicable for the "live" type. */
820             ITrackListEditor* editor = context.playback->EditPlaylist();
821             int to = (int)context.playback->Count();
822 
823             if (offset >= 0 && limit >= 0) {
824                 to = std::min(to, offset + limit);
825             }
826 
827             for (int i = offset; i < to; i++) {
828                 ITrack* track = context.playback->GetTrack(i);
829                 if (idsOnly) {
830                     data.push_back(GetMetadataString(track, key::external_id));
831                 }
832                 else {
833                     data.push_back(this->ReadTrackMetadata(track));
834                 }
835                 if (track) {
836                     track->Release();
837                 }
838             }
839 
840             editor->Release();
841         }
842         else if (type == value::snapshot) {
843             auto snapshot = snapshots.Get(request[message::device_id]);
844             if (snapshot) {
845                 int to = (int) snapshot->Count();
846 
847                 if (offset >= 0 && limit >= 0) {
848                     to = std::min(to, offset + limit);
849                 }
850 
851                 for (int i = offset; i < to; i++) {
852                     ITrack* track = snapshot->GetTrack(i);
853                     if (idsOnly) {
854                         data.push_back(GetMetadataString(track, key::external_id));
855                     }
856                     else {
857                         data.push_back(this->ReadTrackMetadata(track));
858                     }
859                     if (track) {
860                         track->Release();
861                     }
862                 }
863             }
864         }
865 
866         this->RespondWithOptions(connection, request, {
867             { key::data, data },
868             { key::count, data.size() },
869             { key::limit, std::max(0, limit) },
870             { key::offset, offset },
871         });
872     }
873 }
874 
RespondWithQueryAlbums(connection_hdl connection,json & request)875 void WebSocketServer::RespondWithQueryAlbums(connection_hdl connection, json& request) {
876     if (request.find(message::options) != request.end()) {
877         json& options = request[message::options];
878 
879         std::string filter = options.value(key::filter, "");
880         std::string category = options.value(key::category, "");
881         int64_t categoryId = options.value<int64_t>(key::category_id, -1);
882 
883         IMapList* albumList = context.metadataProxy
884             ->QueryAlbums(category.c_str(), categoryId, filter.c_str());
885 
886         json result = json::array();
887 
888         IMap* album;
889         for (size_t i = 0; i < albumList->Count(); i++) {
890             album = albumList->GetAt(i);
891 
892             result.push_back({
893                 { key::title, GetValueString(album) },
894                 { key::id, album->GetId() },
895                 { key::thumbnail_id, album->GetInt64(key::thumbnail_id.c_str()) },
896                 { key::album_artist_id, album->GetInt64(key::album_artist_id.c_str()) },
897                 { key::album_artist, GetMetadataString(album, key::album_artist) }
898             });
899 
900             album->Release();
901         }
902 
903         albumList->Release();
904 
905         this->RespondWithOptions(connection, request, {
906             { key::category, key::album },
907             { key::data, result }
908         });
909 
910         return;
911     }
912 
913     this->RespondWithInvalidRequest(connection, request[message::name], value::invalid);
914 }
915 
RespondWithPlayTracks(connection_hdl connection,json & request)916 void WebSocketServer::RespondWithPlayTracks(connection_hdl connection, json& request) {
917     bool success = false;
918 
919     if (request.find(message::options) != request.end()) {
920         json& options = request[message::options];
921 
922         if (options.find(key::ids) != options.end()) {
923             json& ids = options[key::ids];
924             if (ids.is_array()) {
925                 size_t count = 0;
926                 ITrackListEditor* editor = context.playback->EditPlaylist();
927                 editor->Clear();
928 
929                 for (auto it = ids.begin(); it != ids.end(); ++it) {
930                     editor->Add(*it);
931                     ++count;
932                 }
933 
934                 success = true;
935                 editor->Release();
936             }
937         }
938         else if (options.find(key::external_ids) != options.end()) {
939             json& externalIds = options[key::ids];
940             if (externalIds.is_array()) {
941                 auto externalIdArray = jsonToStringArray(externalIds);
942 
943                 ITrackList* trackList = context.metadataProxy
944                     ->QueryTracksByExternalId(
945                     (const char**)externalIdArray.get(),
946                     externalIds.size());
947 
948                 if (trackList && trackList->Count()) {
949                     ITrackListEditor* editor = context.playback->EditPlaylist();
950                     editor->Clear();
951 
952                     for (size_t i = 0; i < trackList->Count(); i++) {
953                         editor->Add(trackList->GetId(i));
954                     }
955 
956                     editor->Release();
957                     trackList->Release();
958                     success = true;
959                 }
960             }
961         }
962     }
963 
964     if (success) {
965         size_t index = request[message::options].value(key::index, 0);
966         double time = request[message::options].value(key::time, 0.0);
967 
968         context.playback->Play(index);
969 
970         if (time > 0.0) {
971             context.playback->SetPosition(time);
972         }
973 
974         this->RespondWithSuccess(connection, request);
975     }
976     else {
977         this->RespondWithInvalidRequest(connection, request[message::name], value::invalid);
978     }
979 }
980 
QueryTracksByCategory(json & request,int & limit,int & offset)981 ITrackList* WebSocketServer::QueryTracksByCategory(json& request, int& limit, int& offset) {
982     if (request.find(message::options) != request.end()) {
983         json& options = request[message::options];
984 
985         std::string category = options.value(key::category, "");
986         int64_t selectedId = options.value<int64_t>(key::id, -1);
987         auto predicates = options.value(key::predicates, json::array());
988 
989         std::string filter = options.value(key::filter, "");
990 
991         limit = -1, offset = 0;
992         this->GetLimitAndOffset(options, limit, offset);
993 
994         if (predicates.size()) {
995             auto predicateList = jsonToPredicateList(predicates);
996 
997             return context.metadataProxy->QueryTracksByCategories(
998                 predicateList.get(), predicates.size(), filter.c_str(), limit, offset);
999         }
1000         else {
1001             return context.metadataProxy->QueryTracksByCategory(
1002                 category.c_str(), selectedId, filter.c_str(), limit, offset);
1003         }
1004     }
1005 
1006     return nullptr;
1007 }
1008 
RespondWithQueryTracksByCategory(connection_hdl connection,json & request)1009 void WebSocketServer::RespondWithQueryTracksByCategory(connection_hdl connection, json& request) {
1010     int limit, offset;
1011 
1012     ITrackList* tracks = QueryTracksByCategory(request, limit, offset);
1013 
1014     if (tracks && this->RespondWithTracks(connection, request, tracks, limit, offset)) {
1015         return;
1016     }
1017 
1018     this->RespondWithInvalidRequest(connection, request[message::name], value::invalid);
1019 }
1020 
RespondWithListCategories(connection_hdl connection,json & request)1021 void WebSocketServer::RespondWithListCategories(connection_hdl connection, json& request) {
1022     IValueList* result = context.metadataProxy->ListCategories();
1023 
1024     if (result != nullptr) {
1025         json list = json::array();
1026 
1027         for (size_t i = 0; i < result->Count(); i++) {
1028             list[i] = GetValueString(result->GetAt(i));
1029         }
1030 
1031         result->Release();
1032 
1033         this->RespondWithOptions(connection, request, { { key::data, list } });
1034     }
1035     else {
1036         this->RespondWithInvalidRequest(connection, request[message::name], value::invalid);
1037     }
1038 }
1039 
RespondWithQueryCategory(connection_hdl connection,json & request)1040 void WebSocketServer::RespondWithQueryCategory(connection_hdl connection, json& request) {
1041     if (request.find(message::options) != request.end()) {
1042         auto& options = request[message::options];
1043         std::string category = options[key::category];
1044         std::string filter = options.value(key::filter, "");
1045 
1046         /* single predicate */
1047         std::string predicate = options.value(key::predicate_category, "");
1048         int64_t predicateId = options.value<int64_t>(key::predicate_id, -1LL);
1049 
1050         /* multiple predicates */
1051         auto predicates = options.value(key::predicates, json::array());
1052 
1053         if (category.size()) {
1054             IValueList* result;
1055 
1056             if (predicates.size()) {
1057                 auto predicateList = jsonToPredicateList(predicates);
1058                 result = context.metadataProxy
1059                     ->QueryCategoryWithPredicates(
1060                         category.c_str(),
1061                         predicateList.get(),
1062                         predicates.size(),
1063                         filter.c_str());
1064             }
1065             else {
1066                 result = context.metadataProxy
1067                     ->QueryCategoryWithPredicate(
1068                         category.c_str(),
1069                         predicate.c_str(),
1070                         predicateId,
1071                         filter.c_str());
1072             }
1073 
1074             if (result != nullptr) {
1075                 json list = json::array();
1076 
1077                 IValue* current;
1078                 for (size_t i = 0; i < result->Count(); i++) {
1079                     current = result->GetAt(i);
1080 
1081                     list[i] = {
1082                         { key::id, current->GetId() },
1083                         { key::value, GetValueString(current) }
1084                     };
1085                 }
1086 
1087                 result->Release();
1088 
1089                 this->RespondWithOptions(connection, request, {
1090                     { key::category, category },
1091                     { key::data, list }
1092                 });
1093 
1094                 return;
1095             }
1096         }
1097     }
1098 
1099     this->RespondWithInvalidRequest(connection, request[message::name], value::invalid);
1100 }
1101 
RespondWithPlayAllTracks(connection_hdl connection,json & request)1102 void WebSocketServer::RespondWithPlayAllTracks(connection_hdl connection, json& request) {
1103     size_t index = 0;
1104     std::string filter;
1105     double time = 0.0;
1106 
1107     if (request.find(message::options) != request.end()) {
1108         index = request[message::options].value(key::index, 0);
1109         filter = request[message::options].value(key::filter, "");
1110         time = request[message::options].value(key::time, 0.0);
1111     }
1112 
1113     ITrackList* tracks = context.metadataProxy->QueryTracks(filter.c_str());
1114 
1115     if (tracks) {
1116         context.playback->Play(tracks, index);
1117 
1118         if (time > 0.0) {
1119             context.playback->SetPosition(time);
1120         }
1121 
1122         tracks->Release();
1123     }
1124 
1125     RespondWithSuccess(connection, request);
1126 }
1127 
RespondWithPlaySnapshotTracks(connection_hdl connection,json & request)1128 void WebSocketServer::RespondWithPlaySnapshotTracks(connection_hdl connection, json& request) {
1129     auto snapshot = this->snapshots.Get(request[message::device_id]);
1130     if (snapshot) {
1131         size_t index = 0;
1132         double time = 0.0;
1133 
1134         if (request.find(message::options) != request.end()) {
1135             index = request[message::options].value(key::index, 0);
1136             time = request[message::options].value(key::time, 0.0);
1137         }
1138 
1139         context.playback->Play(snapshot, index);
1140 
1141         if (time > 0.0) {
1142             context.playback->SetPosition(time);
1143         }
1144     }
1145     else {
1146         context.playback->Stop();
1147         auto editor = context.playback->EditPlaylist();
1148         editor->Clear();
1149         editor->Release();
1150     }
1151 
1152     RespondWithSuccess(connection, request);
1153 }
1154 
RespondWithPlayTracksByCategory(connection_hdl connection,json & request)1155 void WebSocketServer::RespondWithPlayTracksByCategory(connection_hdl connection, json& request) {
1156     int limit, offset;
1157     ITrackList* tracks = this->QueryTracksByCategory(request, limit, offset);
1158 
1159     if (tracks) {
1160         size_t index = request[message::options].value(key::index, 0);
1161         double time = request[message::options].value(key::time, 0.0);
1162 
1163         context.playback->Play(tracks, index);
1164 
1165         if (time > 0.0) {
1166             context.playback->SetPosition(time);
1167         }
1168 
1169         tracks->Release();
1170     }
1171 
1172     this->RespondWithSuccess(connection, request);
1173 }
1174 
RespondWithEnvironment(connection_hdl connection,json & request)1175 void WebSocketServer::RespondWithEnvironment(connection_hdl connection, json& request) {
1176     this->RespondWithOptions(connection, request, getEnvironment(context));
1177 }
1178 
RespondWithCurrentTime(connection_hdl connection,json & request)1179 void WebSocketServer::RespondWithCurrentTime(connection_hdl connection, json& request) {
1180     auto track = context.playback->GetPlayingTrack();
1181 
1182     this->RespondWithOptions(connection, request, {
1183         { key::playing_current_time, context.playback->GetPosition() },
1184         { key::id, track ? track->GetId() : -1 }
1185     });
1186 }
1187 
RespondWithSavePlaylist(connection_hdl connection,json & request)1188 void WebSocketServer::RespondWithSavePlaylist(connection_hdl connection, json& request) {
1189     /* TODO: a lot of copy/paste between this method and RespondWithAppendToPlaylist */
1190 
1191     auto& options = request[message::options];
1192     int64_t id = options.value<int64_t>(key::playlist_id, 0);
1193     std::string name = options.value(key::playlist_name, "");
1194 
1195     /* by external id (slower, more reliable) */
1196     if (options.find(key::external_ids) != options.end()) {
1197         json& externalIds = options[key::external_ids];
1198 
1199         if (externalIds.is_array()) {
1200             auto externalIdArray = jsonToStringArray(externalIds);
1201 
1202             int64_t newPlaylistId = this->context.metadataProxy
1203                 ->SavePlaylistWithExternalIds(
1204                     (const char**) externalIdArray.get(),
1205                     externalIds.size(),
1206                     name.c_str(),
1207                     id);
1208 
1209             if (newPlaylistId != 0) {
1210                 this->RespondWithOptions(connection, request, {
1211                     { key::playlist_id, newPlaylistId }
1212                 });
1213                 return;
1214             }
1215 
1216             this->RespondWithFailure(connection, request);
1217             return;
1218         }
1219     }
1220     /* by subquery (query_tracks or query_tracks_by_category) */
1221     else if (options.find(key::subquery) != options.end()) {
1222         auto& subquery = options[key::subquery];
1223         std::string type = subquery.value(key::type, "");
1224 
1225         if (subquery.find(message::options) != subquery.end()) {
1226             ITrackList* tracks = nullptr;
1227             int queryLimit, queryOffset;
1228             if (type == request::query_tracks) {
1229                 tracks = this->QueryTracks(subquery, queryLimit, queryOffset);
1230             }
1231             else if (type == request::query_tracks_by_category) {
1232                 tracks = this->QueryTracksByCategory(subquery, queryLimit, queryOffset);
1233             }
1234 
1235             if (tracks) {
1236                 int64_t newPlaylistId = this->context.metadataProxy
1237                     ->SavePlaylistWithTrackList(tracks, name.c_str(), id);
1238 
1239                 tracks->Release();
1240 
1241                 if (newPlaylistId > 0) {
1242                     this->RespondWithOptions(connection, request, {
1243                         { key::playlist_id, newPlaylistId }
1244                     });
1245                     return;
1246                 }
1247 
1248                 this->RespondWithFailure(connection, request);
1249                 return;
1250             }
1251         }
1252     }
1253 
1254     this->RespondWithInvalidRequest(
1255         connection, request[message::name], request[message::id]);
1256 }
1257 
RespondWithRenamePlaylist(connection_hdl connection,json & request)1258 void WebSocketServer::RespondWithRenamePlaylist(connection_hdl connection, json& request) {
1259     auto& options = request[message::options];
1260     int64_t id = options[key::playlist_id];
1261     std::string name = options[key::playlist_name];
1262 
1263     this->context.metadataProxy->RenamePlaylist(id, name.c_str())
1264         ? this->RespondWithSuccess(connection, request)
1265         : this->RespondWithFailure(connection, request);
1266 }
1267 
RespondWithDeletePlaylist(connection_hdl connection,json & request)1268 void WebSocketServer::RespondWithDeletePlaylist(connection_hdl connection, json& request) {
1269     auto& options = request[message::options];
1270     int64_t id = options[key::playlist_id];
1271 
1272     this->context.metadataProxy->DeletePlaylist(id)
1273         ? this->RespondWithSuccess(connection, request)
1274         : this->RespondWithFailure(connection, request);
1275 }
1276 
RespondWithAppendToPlaylist(connection_hdl connection,json & request)1277 void WebSocketServer::RespondWithAppendToPlaylist(connection_hdl connection, json& request) {
1278     /* TODO: a lot of copy/paste between this method and RespondWithSavePlaylist */
1279 
1280     auto& options = request[message::options];
1281     int offset = options.value(key::offset, -1);
1282     int64_t id = options.value<int64_t>(key::playlist_id, 0);
1283 
1284     if (id) {
1285         /* by external id */
1286         if (options.find(key::external_ids) != options.end()) {
1287             json& externalIds = options[key::external_ids];
1288 
1289             if (externalIds.is_array()) {
1290                 auto externalIdArray = jsonToStringArray(externalIds);
1291 
1292                 bool result = this->context.metadataProxy
1293                     ->AppendToPlaylistWithExternalIds(
1294                         id,
1295                         (const char**) externalIdArray.get(),
1296                         externalIds.size(),
1297                         offset);
1298 
1299                 result
1300                     ? this->RespondWithSuccess(connection, request)
1301                     : this->RespondWithFailure(connection, request);
1302 
1303                 return;
1304             }
1305         }
1306         /* by subquery (query_tracks or query_tracks_by_category) */
1307         else if (options.find(key::subquery) != options.end()) {
1308             auto& subquery = options[key::subquery];
1309             std::string type = subquery.value(key::type, "");
1310 
1311             if (subquery.find(message::options) != subquery.end()) {
1312                 ITrackList* tracks = nullptr;
1313                 int queryLimit, queryOffset;
1314                 if (type == request::query_tracks) {
1315                     tracks = this->QueryTracks(subquery, queryLimit, queryOffset);
1316                 }
1317                 else if (type == request::query_tracks_by_category) {
1318                     tracks = this->QueryTracksByCategory(subquery, queryLimit, queryOffset);
1319                 }
1320 
1321                 if (tracks) {
1322                     bool result = this->context.metadataProxy
1323                         ->AppendToPlaylistWithTrackList(id, tracks, offset);
1324 
1325                     tracks->Release();
1326 
1327                     result
1328                         ? this->RespondWithSuccess(connection, request)
1329                         : this->RespondWithFailure(connection, request);
1330 
1331                     return;
1332                 }
1333             }
1334         }
1335     }
1336     /* no id list or external id list */
1337     this->RespondWithInvalidRequest(
1338         connection, request[message::name], request[message::id]);
1339 }
1340 
RespondWithRunIndexer(connection_hdl connection,json & request)1341 void WebSocketServer::RespondWithRunIndexer(connection_hdl connection, json& request) {
1342     auto& options = request[message::options];
1343     auto type = options.value(key::type, value::reindex);
1344     if (type == value::rebuild) {
1345         context.environment->RebuildMetadata();
1346     }
1347     else {
1348         context.environment->ReindexMetadata();
1349     }
1350     this->RespondWithSuccess(connection, request);
1351 }
1352 
RespondWithListOutputDrivers(connection_hdl connection,json & request)1353 void WebSocketServer::RespondWithListOutputDrivers(connection_hdl connection, json& request) {
1354     json outputs = json::array();
1355 
1356     std::string selectedDriverName, selectedDeviceId;
1357 
1358     size_t count = context.environment->GetOutputCount();
1359     for (size_t i = 0; i < count; i++) {
1360         auto output = context.environment->GetDefaultOutput();
1361 
1362         if (output) {
1363             selectedDriverName = output->Name();
1364             auto device = output->GetDefaultDevice();
1365             if (device) {
1366                 selectedDeviceId = device->Id();
1367             }
1368             output->Release();
1369         }
1370 
1371         output = context.environment->GetOutputAtIndex(i);
1372         json devices = json::array();
1373 
1374         devices.push_back({
1375             { key::device_name, "default" },
1376             { key::device_id, "" }
1377         });
1378 
1379         auto deviceList = output->GetDeviceList();
1380         if (deviceList) {
1381             for (size_t j = 0; j < deviceList->Count(); j++) {
1382                 auto device = deviceList->At(j);
1383                 devices.push_back({
1384                     { key::device_name, device->Name() },
1385                     { key::device_id, device->Id() }
1386                 });
1387             }
1388             deviceList->Release();
1389         }
1390 
1391         outputs.push_back({
1392             { key::driver_name, output->Name() },
1393             { key::devices, devices }
1394         });
1395 
1396         output->Release();
1397     }
1398 
1399     this->RespondWithOptions(connection, request, {
1400         { key::all, outputs },
1401         { key::selected, {
1402             { key::driver_name, selectedDriverName },
1403             { key::device_id, selectedDeviceId }
1404         }
1405     }});
1406 }
1407 
RespondWithSetDefaultOutputDriver(connection_hdl connection,json & request)1408 void WebSocketServer::RespondWithSetDefaultOutputDriver(connection_hdl connection, json& request) {
1409     auto& options = request[message::options];
1410     std::string driver = options.value(key::driver_name, "");
1411     if (driver.size()) {
1412         auto output = context.environment->GetOutputWithName(driver.c_str());
1413         if (output) {
1414             std::string device = options.value(key::device_id, "");
1415             output->SetDefaultDevice(device.c_str());
1416             context.environment->SetDefaultOutput(output);
1417             output->Release();
1418             this->RespondWithSuccess(connection, request);
1419             return;
1420         }
1421     }
1422     this->RespondWithFailure(connection, request);
1423 }
1424 
RespondWithGetGainSettings(connection_hdl connection,json & request)1425 void WebSocketServer::RespondWithGetGainSettings(connection_hdl connection, json& request) {
1426     auto replayGainMode = context.environment->GetReplayGainMode();
1427     float preampGain = context.environment->GetPreampGain();
1428 
1429     this->RespondWithOptions(connection, request, {
1430         { key::replaygain_mode, REPLAYGAIN_MODE_TO_STRING.left.find(replayGainMode)->second },
1431         { key::preamp_gain, preampGain }
1432     });
1433 }
1434 
RespondWithSetGainSettings(connection_hdl connection,json & request)1435 void WebSocketServer::RespondWithSetGainSettings(connection_hdl connection, json& request) {
1436     bool reload = false;
1437 
1438     auto& options = request[message::options];
1439 
1440     float currentGain = context.environment->GetPreampGain();
1441     auto currentMode = context.environment->GetReplayGainMode();
1442     auto currentModeString = REPLAYGAIN_MODE_TO_STRING.left.find(currentMode)->second;
1443 
1444     ReplayGainMode newMode = REPLAYGAIN_MODE_TO_STRING.right.find(
1445         options.value(key::replaygain_mode, currentModeString))->second;
1446 
1447     float newGain = options.value(key::preamp_gain, currentGain);
1448 
1449     if (newMode != currentMode) {
1450         context.environment->SetReplayGainMode(newMode);
1451         reload = true;
1452     }
1453 
1454     if (newGain != currentGain) {
1455         context.environment->SetPreampGain(newGain);
1456         reload = true;
1457     }
1458 
1459     if (reload) {
1460         context.environment->ReloadPlaybackOutput();
1461     }
1462 
1463     this->RespondWithSuccess(connection, request);
1464 }
1465 
RespondWithGetEqualizerSettings(connection_hdl connection,json & request)1466 void WebSocketServer::RespondWithGetEqualizerSettings(connection_hdl connection, json& request) {
1467     double values[EqualizerBandCount];
1468     context.environment->GetEqualizerBandValues(values, EqualizerBandCount);
1469     const bool enabled = context.environment->GetEqualizerEnabled();
1470 
1471     std::map<std::string, double> freqToValue;
1472     for (size_t i = 0; i < EqualizerBandCount; i++) {
1473         freqToValue[std::to_string(EqualizerBands[i])] = values[i];
1474     }
1475 
1476     this->RespondWithOptions(connection, request, {
1477         { key::enabled, enabled },
1478         { key::bands, freqToValue }
1479     });
1480 }
1481 
RespondWithSetEqualizerSettings(connection_hdl connection,json & request)1482 void WebSocketServer::RespondWithSetEqualizerSettings(connection_hdl connection, json& request) {
1483     auto& options = request[message::options];
1484 
1485     if (options.find("enabled") != options.end()) {
1486         bool enabled = options.value("enabled", false);
1487         context.environment->SetEqualizerEnabled(enabled);
1488     }
1489 
1490     if (options.find("bands") != options.end()) {
1491         auto bands = options.value("bands", json::array());
1492         if (bands.size() == EqualizerBandCount) {
1493             double values[EqualizerBandCount];
1494             for (size_t i = 0; i < EqualizerBandCount; i++) {
1495                 values[i] = bands[i];
1496             }
1497             context.environment->SetEqualizerBandValues(values, EqualizerBandCount);
1498         }
1499     }
1500 
1501     this->RespondWithSuccess(connection, request);
1502 }
1503 
RespondWithGetTransportType(connection_hdl connection,json & request)1504 void WebSocketServer::RespondWithGetTransportType(connection_hdl connection, json& request) {
1505     auto type = context.environment->GetTransportType();
1506     this->RespondWithOptions(connection, request, {
1507         { key::type, TRANSPORT_TYPE_TO_STRING.left.find(type)->second }
1508     });
1509 }
1510 
RespondWithSetTransportType(connection_hdl connection,json & request)1511 void WebSocketServer::RespondWithSetTransportType(connection_hdl connection, json& request) {
1512     auto& options = request[message::options];
1513 
1514     std::string currentType = TRANSPORT_TYPE_TO_STRING.left
1515         .find(context.environment->GetTransportType())->second;
1516 
1517     auto newType = options.value(key::type, currentType);
1518 
1519     if (currentType != newType) {
1520         auto enumType = TRANSPORT_TYPE_TO_STRING.right.find(newType)->second;
1521         context.environment->SetTransportType(enumType);
1522     }
1523 
1524     this->RespondWithSuccess(connection, request);
1525 }
1526 
RespondWithSnapshotPlayQueue(connection_hdl connection,json & request)1527 void WebSocketServer::RespondWithSnapshotPlayQueue(connection_hdl connection, json& request) {
1528     auto deviceId = request[message::device_id];
1529     this->snapshots.Remove(deviceId);
1530     this->snapshots.Put(deviceId, context.playback->Clone());
1531     this->RespondWithSuccess(connection, request);
1532 }
1533 
RespondWithRemoveTracksFromPlaylist(connection_hdl connection,json & request)1534 void WebSocketServer::RespondWithRemoveTracksFromPlaylist(connection_hdl connection, json& request) {
1535     auto& options = request[message::options];
1536     auto end = options.end();
1537     auto externalIdsIt = options.find(key::external_ids);
1538     auto sortOrdersIt = options.find(key::sort_orders);
1539     int64_t id = options.value<int64_t>(key::playlist_id, 0);
1540     size_t updated = 0;
1541 
1542     bool valid =
1543         id != 0 &&
1544         externalIdsIt != end &&
1545         sortOrdersIt != end &&
1546         (*externalIdsIt).size() == (*sortOrdersIt).size();
1547 
1548     if (valid) {
1549         auto count = (*externalIdsIt).size();
1550         if (count > 0) {
1551             auto ids = jsonToStringArray(*externalIdsIt);
1552             auto orders = jsonToIntArray<int>(*sortOrdersIt);
1553 
1554             updated = this->context.metadataProxy
1555                 ->RemoveTracksFromPlaylist(
1556                     id,
1557                     (const char**)ids.get(),
1558                     orders.get(),
1559                     count);
1560         }
1561     }
1562 
1563     this->RespondWithOptions(connection, request, {
1564         { key::count, updated }
1565     });
1566 }
1567 
BroadcastPlaybackOverview()1568 void WebSocketServer::BroadcastPlaybackOverview() {
1569     {
1570         auto rl = connectionLock.Read();
1571         if (!this->connections.size()) {
1572             return;
1573         }
1574     }
1575 
1576     json options;
1577     this->BuildPlaybackOverview(options);
1578 
1579     /* note that sometimes multiple independent components will request an
1580     overview broadcast, so we always remember the last one, and won't
1581     re-broadcast if status hasn't changed */
1582     std::string newPlaybackOverview = options.dump();
1583     if (newPlaybackOverview != lastPlaybackOverview) {
1584         this->Broadcast(broadcast::playback_overview_changed, options);
1585         this->lastPlaybackOverview = newPlaybackOverview;
1586     }
1587 }
1588 
BroadcastPlayQueueChanged()1589 void WebSocketServer::BroadcastPlayQueueChanged() {
1590     {
1591         auto rl = connectionLock.Read();
1592         if (!this->connections.size()) {
1593             return;
1594         }
1595     }
1596 
1597     json options;
1598     this->Broadcast(broadcast::play_queue_changed, options);
1599 }
1600 
ReadTrackMetadata(ITrack * track)1601 json WebSocketServer::WebSocketServer::ReadTrackMetadata(ITrack* track) {
1602     return {
1603         { key::id, track ? track->GetId() : -1LL },
1604         { key::external_id, GetMetadataString(track, key::external_id) },
1605         { key::title, GetMetadataString(track, key::title) },
1606         { key::track_num, GetMetadataInt32(track, key::track_num.c_str(), 0) },
1607         { key::album, GetMetadataString(track, key::album) },
1608         { key::album_id, GetMetadataInt64(track, key::album_id.c_str()) },
1609         { key::album_artist, GetMetadataString(track, key::album_artist) },
1610         { key::album_artist_id, GetMetadataInt64(track, key::album_artist_id.c_str()) },
1611         { key::artist, GetMetadataString(track, key::artist) },
1612         { key::artist_id, GetMetadataInt64(track, key::visual_artist_id.c_str()) },
1613         { key::genre, GetMetadataString(track, key::genre) },
1614         { key::genre_id, GetMetadataInt64(track, key::visual_genre_id.c_str()) },
1615         { key::thumbnail_id, GetMetadataInt64(track, key::thumbnail_id.c_str()) },
1616     };
1617 }
1618 
BuildPlaybackOverview(json & options)1619 void WebSocketServer::BuildPlaybackOverview(json& options) {
1620     options[key::state] = PLAYBACK_STATE_TO_STRING.left.find(context.playback->GetPlaybackState())->second;
1621     options[key::repeat_mode] = REPEAT_MODE_TO_STRING.left.find(context.playback->GetRepeatMode())->second;
1622     options[key::volume] = context.playback->GetVolume();
1623     options[key::shuffled] = context.playback->IsShuffled();
1624     options[key::muted] = context.playback->IsMuted();
1625     options[key::play_queue_count] = context.playback->Count();
1626     options[key::play_queue_position] = context.playback->GetIndex();
1627     options[key::playing_duration] = context.playback->GetDuration();
1628     options[key::playing_current_time] = context.playback->GetPosition();
1629 
1630     ITrack* track = context.playback->GetPlayingTrack();
1631     if (track) {
1632         options[key::playing_track] = this->ReadTrackMetadata(track);
1633         track->Release();
1634     }
1635 }
1636 
OnOpen(connection_hdl connection)1637 void WebSocketServer::OnOpen(connection_hdl connection) {
1638     auto wl = connectionLock.Write();
1639     connections[connection] = false;
1640 }
1641 
OnClose(connection_hdl connection)1642 void WebSocketServer::OnClose(connection_hdl connection) {
1643     auto wl = connectionLock.Write();
1644     connections.erase(connection);
1645 }
1646 
OnMessage(server * s,connection_hdl hdl,message_ptr msg)1647 void WebSocketServer::OnMessage(server* s, connection_hdl hdl, message_ptr msg) {
1648     auto rl = connectionLock.Read();
1649 
1650     try {
1651         json data = json::parse(msg->get_payload());
1652         std::string type = data[message::type];
1653         if (type == type::request) {
1654             this->HandleRequest(hdl, data);
1655         }
1656     }
1657     catch (std::exception& e) {
1658         std::cerr << "OnMessage failed: " << e.what() << std::endl;
1659         this->RespondWithInvalidRequest(hdl, value::invalid, value::invalid);
1660     }
1661     catch (...) {
1662         std::cerr << "message parse failed: " << msg->get_payload() << "\n";
1663         this->RespondWithInvalidRequest(hdl, value::invalid, value::invalid);
1664     }
1665 }
1666