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