libMeatloaf/lib/meatloaf/network/ws.h
2024-01-08 11:58:15 -06:00

177 lines
5.2 KiB
C++

// WS:// - WebSockets
// https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
// https://en.wikipedia.org/wiki/WebSocket
//
#ifndef MEATLOAF_SCHEME_WS
#define MEATLOAF_SCHEME_WS
#include "meat_io.h"
#include "../../include/global_defines.h"
#include "string_utils.h"
/********************************************************
* Streams
********************************************************/
class CSIOStream: public MStream {
public:
CSIOStream(MFile* file, bool isServer) : m_isServer(isServer) {
// drop ws:// from url and it's OK!
address = websockets::WSInterfaceString(mstr::drop(file->url, 5).c_str());
if(!file->port.empty())
port = std::stoi(file->port);
}
~CSIOStream() {
close();
}
void close() override {
client.close();
}
bool open() override {
if(m_isServer) {
server.listen(port);
client = server.accept();
prepareClientCallbacks();
_is_open = server.available();
}
else {
prepareClientCallbacks();
_is_open = client.connect(address);
// waring - client->poll() required to keep it working!
}
return _is_open;
};
// MStream methods
uint32_t position() override { return 0; };
uint32_t available() override { return INT_MAX; };
bool isOpen() { return _is_open; };
bool seek(uint32_t pos) { return false; };
uint32_t size() { return INT_MAX; };
uint32_t read(uint8_t* buf, uint32_t size) override {
//auto msg = client.readBlocking(); // we don't want to block. We'll store a message from callback in msg
if(!_is_open)
return 0;
if(!lastMsg.isEmpty()) {
auto maxLen = (size < lastMsg.length()) ? size : lastMsg.length();
strncpy((char *)buf, lastMsg.c_str(), maxLen);
Debug_printv("Sending last msg and polling!");
lastMsg = "";
client.poll();
return maxLen;
}
else {
Debug_printv("Polling!");
client.poll();
return 0;
}
}
size_t write(const uint8_t *buf, size_t size) override {
if(!_is_open)
return 0;
websockets::WSInterfaceString message((char *)buf);
client.send(message);
return message.length();
}
protected:
websockets::WSInterfaceString address; // format: www.myserver.com:8080
uint16_t port = 80;
bool _is_open;
bool m_isServer;
websockets::WebsocketsClient client;
websockets::WebsocketsServer server;
websockets::WSInterfaceString lastMsg;
void prepareClientCallbacks() {
client.onMessage([this](websockets::WebsocketsMessage msg){
//
// I think this get called only if you do client.poll()
//
Debug_printv("Got message: %s", msg.data().c_str());
if(msg.isText()) {
//(*msgPtr) = (std::string)msg.data();
this->lastMsg = msg.data();
}
});
client.onEvent([this](websockets::WebsocketsEvent event, String data) {
if(event == websockets::WebsocketsEvent::ConnectionOpened) {
Debug_printv("Socket Connnection Opened");
this->_is_open = true;
} else if(event == websockets::WebsocketsEvent::ConnectionClosed) {
Debug_printv("Socket Connnection Closed");
this->_is_open = false;
} else if(event == websockets::WebsocketsEvent::GotPing) {
Debug_printv("Got a Ping!");
} else if(event == websockets::WebsocketsEvent::GotPong) {
Debug_printv("Got a Pong!");
}
});
};
};
/********************************************************
* File implementations
********************************************************/
class WSFile: public MFile {
public:
WSFile(std::string path): MFile(path) {};
bool isDirectory() override { return false; }
MStream* getSourceStream(std::ios_base::openmode mode=std::ios_base::in) override {
// input stream = SERVER socket
return new CSIOStream(this, true);
};
time_t getLastWrite() override { return 0; };
time_t getCreationTime() override { return 0; };
bool rewindDirectory() override { return false; };
MFile* getNextFileInDir() override { return nullptr; };
bool mkDir() override { return false; };
bool exists() override { return false; };
size_t size() override { return 0; };
bool remove() override { return false; };
bool rename(std::string dest) { return false; };
MStream* getDecodedStream(std::shared_ptr<MStream> src) {
return nullptr;
};
};
/********************************************************
* FS
********************************************************/
class WSFileSystem: public MFileSystem
{
MFile* getFile(std::string path) override {
return new WSFile(path);
}
bool handles(std::string name) {
std::string pattern = "ws:";
return mstr::equals(name, pattern, false);
}
public:
WSFileSystem(): MFileSystem("ws") {};
};
#endif /* MEATLOAF_SCHEME_WS */