Additional scaffolding.

This commit is contained in:
casey langen 2022-03-25 22:02:25 -07:00
parent 31c8505a76
commit e64a748371
6 changed files with 180 additions and 98 deletions

View File

@ -63,6 +63,7 @@ set(CORE_SOURCES
./support/Duration.cpp
./support/Common.cpp
./support/LastFm.cpp
./support/PiggyDebugBackend.cpp
./support/Playback.cpp
./support/Preferences.cpp
./support/PreferenceKeys.cpp

View File

@ -36,8 +36,6 @@
#include <musikcore/net/PiggyWebSocketClient.h>
#include <musikcore/support/Common.h>
#include <musikcore/support/PreferenceKeys.h>
#include <musikcore/support/Preferences.h>
#include <musikcore/runtime/Message.h>
#include <musikcore/version.h>
@ -50,43 +48,33 @@ using ClientMessage = PiggyWebSocketClient::ClientMessage;
using Connection = PiggyWebSocketClient::Connection;
using Message = PiggyWebSocketClient::Message;
static const int64_t kLatencyTimeoutMs = 30000;
static const int64_t kPingIntervalMs = 10000;
static const int kPingMessage = 6000;
static const bool kDisableOfflineQueue = true;
static std::atomic<int> nextMessageId(0);
static inline std::string generateMessageId() {
return "integrated-websocket-client-" + std::to_string(nextMessageId.fetch_add(1));
static inline std::string generateSessionId() {
return "musikcube-" + std::to_string(nextMessageId.fetch_add(1));
}
// static inline std::string createPingRequest() {
// const nlohmann::json authRequestJson = {
// { "name", "ping" },
// { "type" , "request" },
// { "id", generateMessageId() },
// { "device_id", "remote-random-device" },
// { "options", nlohmann::json() }
// };
// return authRequestJson.dump();
// }
static inline std::string createPingJson(const std::string& sessionId) {
const nlohmann::json authRequestJson = {
{ "name", "ping" },
{ "sessionId", sessionId },
{ "data", nlohmann::json() }
};
return authRequestJson.dump();
}
// static inline bool extractRawQueryResult(
// nlohmann::json& responseJson, std::string& rawResult)
// {
// if (responseJson["name"].get<std::string>() != "send_raw_query") {
// return false;
// }
// rawResult = responseJson["options"]["raw_query_data"].get<std::string>();
// return true;
// }
PiggyWebSocketClient::PiggyWebSocketClient(IMessageQueue* messageQueue, Listener* listener)
: messageQueue(nullptr) {
PiggyWebSocketClient::PiggyWebSocketClient(IMessageQueue* messageQueue)
: messageQueue(nullptr)
, sessionId(generateSessionId()) {
this->SetMessageQueue(messageQueue);
rawClient = std::make_unique<RawWebSocketClient>(io);
this->listener = listener;
rawClient->SetMode(RawWebSocketClient::Mode::TLS);
rawClient->SetMode(RawWebSocketClient::Mode::PlainText);
rawClient->SetOpenHandler([this](Connection connection) {
this->SetState(State::Authenticating);
@ -138,28 +126,24 @@ void PiggyWebSocketClient::SetDisconnected(ConnectionError errorCode) {
this->SetState(State::Disconnected);
}
std::string PiggyWebSocketClient::EnqueueMessage(Message message) {
void PiggyWebSocketClient::EnqueueMessage(Message message) {
std::unique_lock<decltype(this->mutex)> lock(this->mutex);
if (kDisableOfflineQueue && this->state != State::Connected) {
return "";
}
if (!message) {
return "";
return;
}
(*message)["sessionId"] = this->sessionId;
if (this->state != State::Connected) {
if (!kDisableOfflineQueue) {
this->pendingMessages.push_back(message);
}
return;
}
auto messageId = generateMessageId();
messageIdToMessage[messageId] = message;
if (this->state == State::Connected) {
this->rawClient->Send(this->connection, message->dump());
}
return messageId;
}
void PiggyWebSocketClient::Connect(
const std::string& host,
unsigned short port,
const std::string& password,
bool useTls)
{
void PiggyWebSocketClient::Connect(const std::string& host, unsigned short port, bool useTls) {
auto newUri = "ws://" + host + ":" + std::to_string(port);
if (newUri != this->uri ||
useTls != this->useTls ||
@ -185,12 +169,9 @@ void PiggyWebSocketClient::Reconnect() {
io.restart();
#endif
auto const prefs = Preferences::ForComponent(core::prefs::components::Settings);
auto const timeout = prefs->GetInt(core::prefs::keys::RemoteLibraryLatencyTimeoutMs, 5000);
this->SetState(State::Connecting);
this->thread = std::make_unique<std::thread>([&, timeout]() {
this->thread = std::make_unique<std::thread>([&]() {
std::string uri;
{
@ -203,7 +184,7 @@ void PiggyWebSocketClient::Reconnect() {
? RawWebSocketClient::Mode::TLS
: RawWebSocketClient::Mode::PlainText;
rawClient->SetMode(mode);
rawClient->SetPongTimeout(timeout);
rawClient->SetPongTimeout(kLatencyTimeoutMs);
rawClient->Connect(uri);
rawClient->Run();
}
@ -228,27 +209,17 @@ void PiggyWebSocketClient::Disconnect() {
void PiggyWebSocketClient::InvalidatePendingMessages() {
std::unique_lock<decltype(this->mutex)> lock(this->mutex);
// for (auto& kv : this->messageIdToMessage) {
// this->listener->OnClientQueryFailed(
// this, kv.first, kv.second, QueryError::Disconnected);
// }
this->messageIdToMessage.clear();
this->pendingMessages.clear();
}
void PiggyWebSocketClient::SendPendingMessages() {
std::unique_lock<decltype(this->mutex)> lock(this->mutex);
// for (auto& kv : this->messageIdToMessage) {
// auto messageId = kv.first;
// auto query = kv.second;
// if (query) {
// this->rawClient->Send(
// this->connection,
// createSendRawQueryRequest(query->SerializeQuery(), messageId));
// }
// }
for (auto& message : this->pendingMessages) {
this->rawClient->Send(this->connection, message->dump());
}
this->pendingMessages.clear();
}
void PiggyWebSocketClient::SetState(State state) {
@ -270,7 +241,7 @@ void PiggyWebSocketClient::SetState(State state) {
}
this->state = state;
this->listener->OnClientStateChanged(this, state, oldState);
this->StateChanged(this, state, oldState);
}
}
@ -284,17 +255,17 @@ void PiggyWebSocketClient::SetMessageQueue(IMessageQueue* messageQueue) {
this->messageQueue = messageQueue;
if (this->messageQueue) {
this->messageQueue->Register(this);
// this->messageQueue->Post(Message::Create(this, kPingMessage), kPingIntervalMs);
this->messageQueue->Post(runtime::Message::Create(this, kPingMessage), kPingIntervalMs);
}
}
/* IMessageTarget */
void PiggyWebSocketClient::ProcessMessage(IMessage& message) {
// if (message.Type() == kPingMessage) {
// std::unique_lock<decltype(this->mutex)> lock(this->mutex);
// if (this->state == State::Connected) {
// this->rawClient->Send(this->connection, createPingRequest());
// }
// this->messageQueue->Post(Message::Create(this, kPingMessage), kPingIntervalMs);
// }
if (message.Type() == kPingMessage) {
std::unique_lock<decltype(this->mutex)> lock(this->mutex);
if (this->state == State::Connected) {
this->rawClient->Send(this->connection, createPingJson(this->sessionId));
}
this->messageQueue->Post(runtime::Message::Create(this, kPingMessage), kPingIntervalMs);
}
}

View File

@ -38,8 +38,10 @@
#include <musikcore/net/RawWebSocketClient.h>
#include <musikcore/runtime/IMessageQueue.h>
#include <sigslot/sigslot.h>
#include <thread>
#include <unordered_map>
#include <deque>
#include <atomic>
#include <memory>
@ -66,36 +68,21 @@ namespace musik { namespace core { namespace net {
enum class ConnectionError : int {
None = 0,
InvalidPassword = 1,
IncompatibleVersion = 2,
ConnectionFailed = 3,
ClosedByServer = 4,
ConnectionFailed = 1,
ClosedByServer = 2,
};
class Listener {
public:
using Client = PiggyWebSocketClient;
using State = Client::State;
virtual void OnClientStateChanged(Client* client, State newState, State oldState) = 0;
};
PiggyWebSocketClient(
musik::core::runtime::IMessageQueue* messageQueue,
Listener* listener);
sigslot::signal3<PiggyWebSocketClient*, State, State> StateChanged;
PiggyWebSocketClient(musik::core::runtime::IMessageQueue* messageQueue);
PiggyWebSocketClient(const PiggyWebSocketClient&) = delete;
virtual ~PiggyWebSocketClient();
void Connect(
const std::string& host,
unsigned short port,
const std::string& password,
bool useTls);
void Connect(const std::string& host, unsigned short port = 8347, bool useTls = false);
void Reconnect();
void Disconnect();
std::string EnqueueMessage(Message message);
void EnqueueMessage(Message message);
State ConnectionState() const;
ConnectionError LastConnectionError() const;
@ -112,16 +99,16 @@ namespace musik { namespace core { namespace net {
ClientPtr rawClient;
Connection connection;
const std::string& sessionId;
boost::asio::io_service io;
std::unique_ptr<std::thread> thread;
mutable std::recursive_mutex mutex;
bool useTls{ false };
std::string uri;
std::unordered_map<std::string, Message> messageIdToMessage;
std::deque<Message> pendingMessages;
std::atomic<bool> quit{ false };
ConnectionError connectionError{ ConnectionError::None };
State state{ State::Disconnected };
Listener* listener{ nullptr };
musik::core::runtime::IMessageQueue* messageQueue;
};

View File

@ -0,0 +1,58 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2004-2021 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#include "pch.hpp"
#include "PiggyDebugBackend.h"
using namespace musik;
PiggyDebugBackend::PiggyDebugBackend(Client client): client(client) {
}
PiggyDebugBackend::~PiggyDebugBackend() {
}
void PiggyDebugBackend::verbose(const std::string& tag, const std::string& string) {
}
void PiggyDebugBackend::info(const std::string& tag, const std::string& string) {
}
void PiggyDebugBackend::warning(const std::string& tag, const std::string& string) {
}
void PiggyDebugBackend::error(const std::string& tag, const std::string& string) {
}

View File

@ -0,0 +1,58 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2004-2021 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#pragma once
#include <memory>
#include <musikcore/debug.h>
#include <musikcore/net/PiggyWebSocketClient.h>
namespace musik {
class PiggyDebugBackend : public musik::debug::IBackend {
public:
using Client = std::shared_ptr<musik::core::net::PiggyWebSocketClient>;
PiggyDebugBackend(Client client);
virtual ~PiggyDebugBackend() override;
virtual void verbose(const std::string& tag, const std::string& string) override;
virtual void info(const std::string& tag, const std::string& string) override;
virtual void warning(const std::string& tag, const std::string& string) override;
virtual void error(const std::string& tag, const std::string& string) override;
private:
Client client;
};
}

View File

@ -58,6 +58,8 @@
#include <musikcore/support/PreferenceKeys.h>
#include <musikcore/sdk/constants.h>
#include <musikcore/support/Common.h>
#include <musikcore/net/PiggyWebSocketClient.h>
#include <musikcore/support/PiggyDebugBackend.h>
#include <boost/locale.hpp>
#include <boost/filesystem/path.hpp>
@ -72,6 +74,7 @@
using namespace musik;
using namespace musik::core;
using namespace musik::core::audio;
using namespace musik::core::net;
using namespace musik::cube;
using namespace cursespp;
@ -113,9 +116,13 @@ int main(int argc, char* argv[]) {
std::string errorFn = core::GetDataDirectory() + "stderr.txt";
freopen(errorFn.c_str(), "w", stderr);
auto piggyClient = std::make_shared<PiggyWebSocketClient>(&Window::MessageQueue());
piggyClient->Connect("172.31.16.1");
auto piggyLogger = new PiggyDebugBackend(piggyClient);
auto fileLogger = new debug::SimpleFileBackend();
auto consoleLogger = new ConsoleLogger(Window::MessageQueue());
debug::Start({ fileLogger, consoleLogger });
debug::Start({ fileLogger, consoleLogger, piggyLogger });
plugin::Init();