ggwave-gui : wip file sharing support

This commit is contained in:
Georgi Gerganov
2020-12-30 12:04:38 +02:00
parent 442479bbaf
commit 82b0748830
12 changed files with 516 additions and 48 deletions

View File

@@ -4,6 +4,10 @@
#include "ggwave-common.h"
#include "ggsock/communicator.h"
#include "ggsock/file-server.h"
#include "ggsock/serialization.h"
#include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
@@ -17,6 +21,8 @@
#include <mutex>
#include <thread>
#include <vector>
#include <sstream>
#include <cstring>
#if defined(IOS) || defined(ANDROID)
#include "imgui-wrapper/icons_font_awesome.h"
@@ -29,14 +35,47 @@
#define ICON_FA_PLAY_CIRCLE ""
#define ICON_FA_ARROW_CIRCLE_DOWN "V"
#define ICON_FA_PASTE "P"
#define ICON_FA_FILE ""
#endif
namespace {
char * toTimeString(const std::chrono::system_clock::time_point & tp) {
time_t t = std::chrono::system_clock::to_time_t(tp);
std::tm * ptm = std::localtime(&t);
static char buffer[32];
std::strftime(buffer, 32, "%H:%M:%S", ptm);
return buffer;
}
void ScrollWhenDraggingOnVoid(const ImVec2& delta, ImGuiMouseButton mouse_button) {
ImGuiContext& g = *ImGui::GetCurrentContext();
ImGuiWindow* window = g.CurrentWindow;
bool hovered = false;
bool held = false;
ImGuiButtonFlags button_flags = (mouse_button == 0) ? ImGuiButtonFlags_MouseButtonLeft : (mouse_button == 1) ? ImGuiButtonFlags_MouseButtonRight : ImGuiButtonFlags_MouseButtonMiddle;
if (g.HoveredId == 0) // If nothing hovered so far in the frame (not same as IsAnyItemHovered()!)
ImGui::ButtonBehavior(window->Rect(), window->GetID("##scrolldraggingoverlay"), &hovered, &held, button_flags);
if (held && delta.x != 0.0f)
ImGui::SetScrollX(window, window->Scroll.x + delta.x);
if (held && delta.y != 0.0f)
ImGui::SetScrollY(window, window->Scroll.y + delta.y);
}
}
static const char * kFileBroadcastPrefix = "\xbc";
struct Message {
enum Type {
Text,
FileBroadcast,
};
bool received;
std::chrono::system_clock::time_point timestamp;
std::string data;
int protocolId;
float volume;
Type type;
};
struct GGWaveStats {
@@ -112,36 +151,183 @@ struct Buffer {
Input inputUI;
};
char * toTimeString(const std::chrono::system_clock::time_point & tp) {
time_t t = std::chrono::system_clock::to_time_t(tp);
std::tm * ptm = std::localtime(&t);
static char buffer[32];
std::strftime(buffer, 32, "%H:%M:%S", ptm);
return buffer;
}
void ScrollWhenDraggingOnVoid(const ImVec2& delta, ImGuiMouseButton mouse_button) {
ImGuiContext& g = *ImGui::GetCurrentContext();
ImGuiWindow* window = g.CurrentWindow;
bool hovered = false;
bool held = false;
ImGuiButtonFlags button_flags = (mouse_button == 0) ? ImGuiButtonFlags_MouseButtonLeft : (mouse_button == 1) ? ImGuiButtonFlags_MouseButtonRight : ImGuiButtonFlags_MouseButtonMiddle;
if (g.HoveredId == 0) // If nothing hovered so far in the frame (not same as IsAnyItemHovered()!)
ImGui::ButtonBehavior(window->Rect(), window->GetID("##scrolldraggingoverlay"), &hovered, &held, button_flags);
if (held && delta.x != 0.0f)
ImGui::SetScrollX(window, window->Scroll.x + delta.x);
if (held && delta.y != 0.0f)
ImGui::SetScrollY(window, window->Scroll.y + delta.y);
}
GGWave * g_ggWave;
Buffer g_buffer;
std::atomic<bool> g_isRunning;
bool g_focusFileSend = false;
GGSock::FileServer g_fileServer;
bool g_hasFileInfos = false;
bool g_hasRequestedFiles = false;
GGSock::FileServer::TFileInfos g_fileInfos;
std::map<GGSock::FileServer::TURI, GGSock::FileServer::FileData> g_receivedFiles;
int g_remotePort = 23045;
std::string g_remoteIP = "127.0.0.1";
GGSock::Communicator g_fileClient(false);
int g_shareId = 0;
std::string g_shareFilename;
int g_deleteId = 0;
std::string g_deleteFilename;
int g_receivedId = 0;
int getShareId() {
return g_shareId;
}
const char * getShareFilename() {
return g_shareFilename.data();
}
int getDeleteId() {
return g_deleteId;
}
const char * getDeleteFilename() {
return g_deleteFilename.data();
}
int getReceivedId() {
return g_receivedId;
}
std::vector<const char*> getReceivedFilename() {
std::vector<const char*> result;
for (const auto & file : g_receivedFiles) {
result.push_back(file.second.info.filename.c_str());
}
return result;
}
std::vector<const char *> getReceivedDataBuffer() {
std::vector<const char*> result;
for (const auto & file : g_receivedFiles) {
result.push_back(file.second.data.data());
}
return result;
}
std::vector<size_t> getReceivedDataSize() {
std::vector<size_t> result;
for (const auto & file : g_receivedFiles) {
result.push_back(file.second.data.size());
}
return result;
}
void clearFiles() {
g_fileServer.clearAllFiles();
}
void addFile(
const char * uri,
const char * filename,
const char * dataBuffer,
size_t dataSize) {
GGSock::FileServer::FileData file;
file.info.uri = uri;
file.info.filename = filename;
file.data.resize(dataSize);
std::memcpy(file.data.data(), dataBuffer, dataSize);
g_fileServer.addFile(std::move(file));
g_focusFileSend = true;
}
void addFile(
const char * uri,
const char * filename,
std::vector<char> && data) {
GGSock::FileServer::FileData file;
file.info.uri = uri;
file.info.filename = filename;
file.data = std::move(data);
g_fileServer.addFile(std::move(file));
g_focusFileSend = true;
}
std::string generateFileBroadcastMessage() {
// todo : to binary
std::string result;
result = kFileBroadcastPrefix;
result += ' ';
result += GGSock::Communicator::getLocalAddress();
result += ' ';
result += std::to_string(g_fileServer.getParameters().listenPort);
result += ' ';
result += std::to_string(rand()%32000); // todo : generated key should be used to authorize incoming messages
return result;
}
bool isFileBroadcastMessage(const std::string & message) {
bool result = true;
auto pSrc = kFileBroadcastPrefix;
auto pDst = message.data();
while (pSrc != 0) {
if (*pDst == 0 || *pSrc++ != *pDst++) {
result = false;
break;
}
}
return result;
}
std::thread initMain() {
g_isRunning = true;
g_ggWave = GGWave_instance();
g_fileServer.init({});
g_fileClient.setErrorCallback([](GGSock::Communicator::TErrorCode code) {
printf("Disconnected with code = %d\n", code);
});
g_fileClient.setMessageCallback(GGSock::FileServer::MsgFileInfosResponse, [&](const char * dataBuffer, size_t dataSize) {
printf("Received message %d, size = %d\n", GGSock::FileServer::MsgFileInfosResponse, (int) dataSize);
size_t offset = 0;
GGSock::Unserialize()(g_fileInfos, dataBuffer, dataSize, offset);
for (const auto & info : g_fileInfos) {
printf(" - %s : %s (size = %d, chunks = %d)\n", info.second.uri.c_str(), info.second.filename.c_str(), (int) info.second.filesize, (int) info.second.nChunks);
g_receivedFiles[info.second.uri].info = info.second;
g_receivedFiles[info.second.uri].data.resize(info.second.filesize);
}
g_hasFileInfos = true;
return 0;
});
g_fileClient.setMessageCallback(GGSock::FileServer::MsgFileChunkResponse, [&](const char * dataBuffer, size_t dataSize) {
GGSock::FileServer::FileChunkResponseData data;
size_t offset = 0;
GGSock::Unserialize()(data, dataBuffer, dataSize, offset);
//printf("Received chunk %d for file '%s', size = %d\n", data.chunkId, data.uri.c_str(), (int) data.data.size());
std::memcpy(g_receivedFiles[data.uri].data.data() + data.pStart, data.data.data(), data.pLen);
return 0;
});
return std::thread([&]() {
Input inputCurrent;
@@ -171,14 +357,16 @@ std::thread initMain() {
lastRxDataLength = g_ggWave->takeRxData(lastRxData);
if (lastRxDataLength > 0) {
auto message = std::string((char *) lastRxData.data(), lastRxDataLength);
g_buffer.stateCore.update = true;
g_buffer.stateCore.flags.newMessage = true;
g_buffer.stateCore.message = {
true,
std::chrono::system_clock::now(),
std::string((char *) lastRxData.data(), lastRxDataLength),
std::move(message),
g_ggWave->getRxProtocolId(),
0,
isFileBroadcastMessage(message) ? Message::FileBroadcast : Message::Text,
};
}
@@ -214,6 +402,34 @@ std::thread initMain() {
}
void renderMain() {
g_fileServer.update();
if (g_fileClient.isConnected()) {
if (!g_hasFileInfos) {
g_fileClient.send(GGSock::FileServer::MsgFileInfosRequest);
} else if (g_hasRequestedFiles == false) {
printf("Requesting files ...\n");
for (const auto & fileInfo : g_fileInfos) {
for (int i = 0; i < fileInfo.second.nChunks; ++i) {
GGSock::FileServer::FileChunkRequestData data;
data.uri = fileInfo.second.uri;
data.chunkId = i;
data.nChunksHave = 0;
data.nChunksExpected = fileInfo.second.nChunks;
GGSock::SerializationBuffer buffer;
GGSock::Serialize()(data, buffer);
g_fileClient.send(GGSock::FileServer::MsgFileChunkRequest, buffer.data(), (int32_t) buffer.size());
g_fileClient.update();
}
}
g_hasRequestedFiles = true;
g_receivedId++;
}
}
g_fileClient.update();
static State stateCurrent;
{
@@ -224,15 +440,23 @@ void renderMain() {
enum class WindowId {
Settings,
Messages,
Files,
Spectrum,
};
enum SubWindowId {
Send,
Receive,
};
struct Settings {
int protocolId = 1;
float volume = 0.10f;
};
static WindowId windowId = WindowId::Messages;
static SubWindowId subWindowId = SubWindowId::Send;
static Settings settings;
const double tHoldContextPopup = 0.5f;
@@ -290,6 +514,12 @@ void renderMain() {
stateCurrent.update = false;
}
if (g_focusFileSend) {
windowId = WindowId::Files;
subWindowId = SubWindowId::Send;
g_focusFileSend = false;
}
if (lastMouseButtonLeft == 0 && ImGui::GetIO().MouseDown[0] == 1) {
ImGui::GetIO().MouseDelta = { 0.0, 0.0 };
}
@@ -307,6 +537,8 @@ void renderMain() {
#endif
const float menuButtonHeight = 24.0f + 2.0f*style.ItemSpacing.y;
const auto & mouse_delta = ImGui::GetIO().MouseDelta;
ImGui::SetNextWindowPos({ 0, 0, });
ImGui::SetNextWindowSize(displaySize);
ImGui::Begin("Main", nullptr,
@@ -322,12 +554,17 @@ void renderMain() {
windowId = WindowId::Settings;
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_COMMENT_ALT " Messages", { 0.5f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight })) {
if (ImGui::Button(ICON_FA_COMMENT_ALT " Messages", { 0.35f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight })) {
windowId = WindowId::Messages;
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_FILE " Files", { 0.40f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight })) {
windowId = WindowId::Files;
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_SIGNAL " Spectrum", { 1.0f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight })) {
windowId = WindowId::Spectrum;
}
@@ -336,7 +573,7 @@ void renderMain() {
ImGui::BeginChild("Settings:main", ImGui::GetContentRegionAvail(), true);
ImGui::Text("%s", "");
ImGui::Text("%s", "");
ImGui::Text("Waver v1.2.0");
ImGui::Text("Waver v1.3.0");
ImGui::Separator();
ImGui::Text("%s", "");
@@ -460,7 +697,6 @@ void renderMain() {
static bool isHoldingInput = false;
static int messageIdHolding = 0;
const auto & mouse_delta = ImGui::GetIO().MouseDelta;
const float tMessageFlyIn = 0.3f;
// we need this because we push messages in the next loop
@@ -493,9 +729,16 @@ void renderMain() {
p0.x -= style.ItemSpacing.x;
p0.y -= 0.5f*style.ItemSpacing.y;
auto col = style.Colors[ImGuiCol_Text];
col.w = interp;
ImGui::TextColored(col, "%s", message.data.c_str());
if (message.type == Message::FileBroadcast) {
auto col = ImVec4 { 0.0f, 1.0f, 1.0f, 1.0f };
col.w = interp;
ImGui::TextColored(col, "-=[ File Broadcast ]=-");
ImGui::TextColored(col, "%s", message.data.c_str());
} else {
auto col = style.Colors[ImGuiCol_Text];
col.w = interp;
ImGui::TextColored(col, "%s", message.data.c_str());
}
auto p1 = ImGui::GetCursorScreenPos();
p1.x = p00.x + ImGui::GetContentRegionAvailWidth();
@@ -536,7 +779,7 @@ void renderMain() {
if (ImGui::Button("Resend")) {
g_buffer.inputUI.update = true;
g_buffer.inputUI.message = { false, std::chrono::system_clock::now(), messageSelected.data, messageSelected.protocolId, settings.volume };
g_buffer.inputUI.message = { false, std::chrono::system_clock::now(), messageSelected.data, messageSelected.protocolId, settings.volume, Message::Text };
messageHistory.push_back(g_buffer.inputUI.message);
ImGui::CloseCurrentPopup();
@@ -551,6 +794,26 @@ void renderMain() {
ImGui::CloseCurrentPopup();
}
//if (messageSelected.received && messageSelected.type == Message::FileBroadcast) {
if (messageSelected.type == Message::FileBroadcast) {
ImGui::SameLine();
ImGui::TextDisabled("|");
ImGui::SameLine();
if (ImGui::Button("Receive")) {
std::string tmp = messageSelected.data.data() + strlen(kFileBroadcastPrefix);
std::stringstream ss(tmp);
ss >> g_remoteIP;
ss >> g_remotePort;
g_fileClient.disconnect();
g_hasFileInfos = false;
g_hasRequestedFiles = false;
ImGui::CloseCurrentPopup();
}
}
ImGui::EndPopup();
}
@@ -692,7 +955,7 @@ void renderMain() {
ImGui::SameLine();
if ((ImGui::Button(sendButtonText) || doSendMessage) && inputBuf[0] != 0) {
g_buffer.inputUI.update = true;
g_buffer.inputUI.message = { false, std::chrono::system_clock::now(), std::string(inputBuf), settings.protocolId, settings.volume };
g_buffer.inputUI.message = { false, std::chrono::system_clock::now(), std::string(inputBuf), settings.protocolId, settings.volume, Message::Text };
messageHistory.push_back(g_buffer.inputUI.message);
@@ -707,6 +970,131 @@ void renderMain() {
}
}
if (windowId == WindowId::Files) {
const float subWindowButtonHeight = menuButtonHeight;
if (ImGui::Button("Send", { 0.50f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) {
subWindowId = SubWindowId::Send;
}
ImGui::SameLine();
if (ImGui::Button("Receive", { 1.0f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) {
subWindowId = SubWindowId::Receive;
}
switch (subWindowId) {
case SubWindowId::Send:
{
{
const auto wSize = ImVec2 { ImGui::GetContentRegionAvailWidth(), 0.60f*ImGui::GetContentRegionAvail().y };
ImGui::BeginChild("Files:Send:fileInfos", wSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
//ImGui::PushTextWrapPos();
auto fileInfos = g_fileServer.getFileInfos();
for (const auto & fileInfo : fileInfos) {
ImGui::PushID(fileInfo.first);
ImGui::Text("File: '%s' (%4.2f MB)\n", fileInfo.second.filename.c_str(), float(fileInfo.second.filesize)/1024.0f/1024.0f);
if (ImGui::Button("Save")) {
g_shareFilename = fileInfo.second.filename;
g_shareId++;
}
ImGui::SameLine();
if (ImGui::Button("Clear")) {
g_deleteFilename = fileInfo.second.filename;
g_deleteId++;
}
ImGui::PopID();
}
//ImGui::PopTextWrapPos();
ScrollWhenDraggingOnVoid(ImVec2(-mouse_delta.x, -mouse_delta.y), ImGuiMouseButton_Left);
ImGui::EndChild();
if (ImGui::Button("Broadcast", { 0.40f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) {
g_buffer.inputUI.update = true;
g_buffer.inputUI.message = {
false,
std::chrono::system_clock::now(),
::generateFileBroadcastMessage(),
settings.protocolId,
settings.volume,
Message::FileBroadcast
};
messageHistory.push_back(g_buffer.inputUI.message);
g_fileServer.startListening();
}
ImGui::SameLine();
if (ImGui::Button("Stop", { 0.50f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) {
g_fileServer.stopListening();
}
ImGui::SameLine();
if (ImGui::Button("Clear", { 1.0f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) {
g_deleteFilename = "###ALL-FILES###";
g_deleteId++;
}
}
{
const auto wSize = ImVec2 { ImGui::GetContentRegionAvailWidth(), ImGui::GetContentRegionAvail().y };
ImGui::BeginChild("Files:Send:clientInfos", wSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
if (g_fileServer.isListening() == false) {
ImGui::TextColored({ 1.0f, 1.0f, 0.0f, 1.0f }, "Not accepting new connections");
} else {
ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 1.0f }, "Accepting new connections at %s:%d",
GGSock::Communicator::getLocalAddress().c_str(), g_fileServer.getParameters().listenPort);
}
auto clientInfos = g_fileServer.getClientInfos();
if (clientInfos.empty()) {
ImGui::Text("No peers currently connected ..");
} else {
for (const auto & clientInfo : clientInfos) {
ImGui::Text("Peer: %s\n", clientInfo.second.address.c_str());
}
}
ScrollWhenDraggingOnVoid(ImVec2(-mouse_delta.x, -mouse_delta.y), ImGuiMouseButton_Left);
ImGui::EndChild();
}
}
break;
case SubWindowId::Receive:
{
const auto wSize = ImGui::GetContentRegionAvail();
ImGui::BeginChild("Files:Receive:main", wSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
ImGui::Text("Remote IP: %s", g_remoteIP.c_str());
ImGui::Text("Remote Port: %d", g_remotePort);
if (ImGui::Button("Connect", { 0.50f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) {
if (g_fileClient.connect(g_remoteIP, g_remotePort, 0)) {
printf("Started connecting ...\n");
}
}
ImGui::SameLine();
if (ImGui::Button("Disconnect", { 1.00f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) {
if (g_fileClient.disconnect()) {
printf("Stopped connecting\n");
}
}
ScrollWhenDraggingOnVoid(ImVec2(-mouse_delta.x, -mouse_delta.y), ImGuiMouseButton_Left);
ImGui::EndChild();
}
break;
};
}
if (windowId == WindowId::Spectrum) {
ImGui::BeginChild("Spectrum:main", ImGui::GetContentRegionAvail(), true);
ImGui::PushTextWrapPos();