Skip to content

Commit

Permalink
TgBot++: clientapp/win32: Don't block UI thread for socket-op
Browse files Browse the repository at this point in the history
- Also introduce BlockingOperation class, trying to commonize code
  • Loading branch information
Royna2544 committed Jun 2, 2024
1 parent 8b62979 commit 4c7fa0f
Show file tree
Hide file tree
Showing 10 changed files with 276 additions and 88 deletions.
27 changes: 27 additions & 0 deletions clientapp/windows/TgBotClient/BlockingOperation.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#define _CRT_SECURE_NO_WARNINGS

#include "BlockingOperation.h"

#include <WinUser.h>

#include "UIComponents.h"

bool BlockingOperation::shouldBlockRequest(HWND hDlg) const {
if (m_enabled) {
auto &loader = StringLoader::getInstance();
MessageBox(hDlg, loader.getString(IDS_OPERATION_ACTIVE).c_str(),
loader.getString(IDS_FAILED).c_str(), WARNING_DIALOG);
return true;
}
return false;
}

void BlockingOperation::start() {
SetCursor(LoadCursor(NULL, IDC_WAIT));
m_enabled = true;
}

void BlockingOperation::stop() {
SetCursor(LoadCursor(NULL, IDC_ARROW));
m_enabled = false;
}
13 changes: 13 additions & 0 deletions clientapp/windows/TgBotClient/BlockingOperation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once

#include <wtypes.h>

class BlockingOperation {
public:
bool shouldBlockRequest(HWND hDlg) const;
void start();
void stop();

private:
bool m_enabled;
};
126 changes: 92 additions & 34 deletions clientapp/windows/TgBotClient/SendFileToChat.cpp
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@

#define _CRT_SECURE_NO_WARNINGS
#include <absl/log/log.h>
#include <process.h>
#include <tchar.h>

#include <array>
#include <cstdint>
#include <optional>

#include "../../../src/include/TryParseStr.hpp"
#include "BlockingOperation.h"
#include "TgBotSocketIntf.h"
#include "UIComponents.h"
#include <optional>
#include <tchar.h>
#include <absl/log/log.h>

namespace {

struct SendFileInput {
ChatId chatid;
char filePath[MAX_PATH_SIZE];
HWND dialog;
};
struct SendFileResult {
bool success;
char reason[MAX_MSG_SIZE];
};

std::optional<StringLoader::String> OpenFilePicker(HWND hwnd) {
OPENFILENAME ofn; // Common dialog box structure
OPENFILENAME ofn; // Common dialog box structure
TCHAR szFile[MAX_PATH_SIZE]{}; // Buffer for file name
HANDLE hf = nullptr; // File handle

Expand All @@ -34,73 +46,95 @@ std::optional<StringLoader::String> OpenFilePicker(HWND hwnd) {
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

// Display the Open dialog box
if (GetOpenFileName(&ofn) == TRUE)
return ofn.lpstrFile;
if (GetOpenFileName(&ofn) == TRUE) return ofn.lpstrFile;
return std::nullopt;
}

} // namespace
unsigned __stdcall SendFileTask(void* param) {
const auto* in = (SendFileInput*)param;
SendFileResult* result = nullptr;

result = allocMem<SendFileResult>();
if (result == nullptr) {
PostMessage(in->dialog, WM_SENDFILE_RESULT, 0, (LPARAM) nullptr);
free(param);
return 0;
}
sendFileToChat(in->chatid, in->filePath, [result](const GenericAck* data) {
result->success = data->result == AckType::SUCCESS;
if (!result->success) {
strncpy(result->reason, data->error_msg,
static_cast<size_t>(MAX_MSG_SIZE) - 1);
}
});

PostMessage(in->dialog, WM_SENDFILE_RESULT, 0, (LPARAM)result);
free(param);
return 0;
}

} // namespace

INT_PTR CALLBACK SendFileToChat(HWND hDlg, UINT message, WPARAM wParam,
LPARAM lParam) {
LPARAM lParam) {
static HWND hChatId;
static HWND hFileText;
static HWND hFileButton;
static BlockingOperation blk;

UNREFERENCED_PARAMETER(lParam);

switch (message) {
case WM_INITDIALOG:
hChatId = GetDlgItem(hDlg, IDC_CHATID);
hFileButton = GetDlgItem(hDlg, IDC_BROWSE);
hFileText = GetDlgItem(hDlg, IDC_SEL_FILE);
SetFocus(hChatId);
return (INT_PTR)TRUE;
return DIALOG_OK;

case WM_COMMAND:
if (blk.shouldBlockRequest(hDlg)) {
return DIALOG_OK;
}

switch (LOWORD(wParam)) {
case IDSEND: {
ChatId chatid = 0;
std::array<TCHAR, 64 + 1> chatidbuf = {};
std::array<char, MAX_PATH_SIZE> pathbuf = {};
StringLoader::String errtext;
std::optional<StringLoader::String> errtext;
bool fail = false;
auto& loader = StringLoader::getInstance();

GetDlgItemText(hDlg, IDC_CHATID, chatidbuf.data(),
chatidbuf.size() - 1);
chatidbuf.size() - 1);
GetDlgItemTextA(hDlg, IDC_SEL_FILE, pathbuf.data(),
pathbuf.size());

if (Length(chatidbuf.data()) == 0) {
errtext = loader.getString(IDS_CHATID_EMPTY);
fail = true;
} else if (!try_parse(chatidbuf.data(), &chatid)) {
errtext = loader.getString(IDS_CHATID_NOTINT);
fail = true;
}
if (!fail) {
std::string serverReason;
GetDlgItemTextA(hDlg, IDC_SEL_FILE, pathbuf.data(), pathbuf.size());
fail = !sendFileToChat(
chatid, pathbuf.data(),
[&serverReason](const GenericAck* data) {
serverReason = data->error_msg;
});
if (fail) {
errtext = loader.getString(IDS_CMD_FAILED_SVR) + kLineBreak;
errtext += loader.getString(IDS_CMD_FAILED_SVR_RSN);
errtext += StringLoader::String(serverReason.begin(), serverReason.end());
if (!errtext.has_value()) {
auto* in = allocMem<SendFileInput>();

if (in) {
blk.start();
in->chatid = chatid;
in->dialog = hDlg;
strncpy(in->filePath, pathbuf.data(),
MAX_PATH_SIZE - 1);
in->filePath[MAX_PATH_SIZE - 1] = 0;
_beginthreadex(NULL, 0, SendFileTask, in, 0, NULL);
} else {
MessageBox(
hDlg,
loader.getString(IDS_SUCCESS_FILESENT).c_str(),
loader.getString(IDS_SUCCESS).c_str(),
MB_ICONINFORMATION | MB_OK);
errtext = _T("Failed to allocate memory");
}
}
if (fail) {
MessageBox(hDlg, errtext.c_str(),
if (errtext) {
MessageBox(hDlg, errtext->c_str(),
loader.getString(IDS_FAILED).c_str(),
MB_ICONERROR | MB_OK);
ERROR_DIALOG);
}
return DIALOG_OK;
}
Expand All @@ -117,6 +151,30 @@ INT_PTR CALLBACK SendFileToChat(HWND hDlg, UINT message, WPARAM wParam,
break;
};
break;
case WM_SENDFILE_RESULT: {
if (lParam == NULL) {
return DIALOG_OK;
}
const auto* res = reinterpret_cast<SendFileResult*>(lParam);
auto& loader = StringLoader::getInstance();

if (!res->success) {
StringLoader::String errtext;
errtext = loader.getString(IDS_CMD_FAILED_SVR) + kLineBreak;
errtext += loader.getString(IDS_CMD_FAILED_SVR_RSN);
errtext += charToWstring(res->reason);
MessageBox(hDlg, errtext.c_str(),
loader.getString(IDS_FAILED).c_str(),
ERROR_DIALOG);
} else {
MessageBox(hDlg, loader.getString(IDS_SUCCESS_FILESENT).c_str(),
loader.getString(IDS_SUCCESS).c_str(),
INFO_DIALOG);
}
blk.stop();
free(reinterpret_cast<void*>(lParam));
return DIALOG_OK;
}
}
return DIALOG_NO;
}
109 changes: 80 additions & 29 deletions clientapp/windows/TgBotClient/SendMessageToChat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,68 @@
#include "../../../src/include/TryParseStr.hpp"
#include "TgBotSocketIntf.h"
#include "UIComponents.h"
#include "BlockingOperation.h"
#include <process.h>

namespace {

struct SendMsgInput {
ChatId chatid;
char message[MAX_PATH_SIZE];
HWND dialog;
};
struct SendMsgResult {
bool success;
char reason[MAX_MSG_SIZE];
};

unsigned __stdcall SendMsgTask(void* param) {
const auto* in = (SendMsgInput*)param;
SendMsgResult* result = nullptr;

result = allocMem<SendMsgResult>();
if (result == nullptr) {
PostMessage(in->dialog, WM_SENDMSG_RESULT, 0, (LPARAM) nullptr);
free(param);
return 0;
}
sendMessageToChat(in->chatid, in->message, [result](const GenericAck* data) {
result->success = data->result == AckType::SUCCESS;
if (!result->success) {
strncpy(result->reason, data->error_msg,
static_cast<size_t>(MAX_MSG_SIZE) - 1);
}
});

PostMessage(in->dialog, WM_SENDMSG_RESULT, 0, (LPARAM)result);
free(param);
return 0;
}
} // namespace

INT_PTR CALLBACK SendMsgToChat(HWND hDlg, UINT message, WPARAM wParam,
LPARAM lParam) {
static HWND hChatId;
static HWND hMsgText;
static BlockingOperation blk;

UNREFERENCED_PARAMETER(lParam);
switch (message) {
case WM_INITDIALOG:
hChatId = GetDlgItem(hDlg, IDC_CHATID);
hMsgText = GetDlgItem(hDlg, IDC_MESSAGETXT);
return (INT_PTR)TRUE;
return DIALOG_OK;

case WM_COMMAND:
if (blk.shouldBlockRequest(hDlg)) {
return DIALOG_OK;
}
switch (LOWORD(wParam)) {
case IDSEND: {
ChatId chatid = 0;
std::array<char, MAX_MSG_SIZE> msgbuf = {};
std::array<TCHAR, 64 + 1> chatidbuf = {};
StringLoader::String errtext;
bool fail = false;
std::optional<StringLoader::String> errtext;
auto& loader = StringLoader::getInstance();

GetDlgItemText(hDlg, IDC_CHATID, chatidbuf.data(),
Expand All @@ -34,40 +75,26 @@ INT_PTR CALLBACK SendMsgToChat(HWND hDlg, UINT message, WPARAM wParam,
msgbuf.size());
if (Length(chatidbuf.data()) == 0) {
errtext = loader.getString(IDS_CHATID_EMPTY);
fail = true;
} else if (Length(msgbuf.data()) == 0) {
errtext = loader.getString(IDS_MSG_EMPTY);
fail = true;
} else if (!try_parse(chatidbuf.data(), &chatid)) {
errtext = loader.getString(IDS_CHATID_NOTINT);
fail = true;
}
if (!fail) {
std::string serverReason;
fail = !sendMessageToChat(
chatid, msgbuf.data(),
[&serverReason](const GenericAck* data) {
serverReason = data->error_msg;
});
if (fail) {
errtext = loader.getString(IDS_CMD_FAILED_SVR) +
kLineBreak;
errtext += loader.getString(IDS_CMD_FAILED_SVR_RSN);
errtext += StringLoader::String(
serverReason.begin(), serverReason.end());
if (!errtext) {
auto* in = allocMem<SendMsgInput>();

if (in) {
blk.start();
in->chatid = chatid;
in->dialog = hDlg;
strncpy(in->message, msgbuf.data(),
MAX_MSG_SIZE - 1);
in->message[MAX_MSG_SIZE - 1] = 0;
_beginthreadex(NULL, 0, SendMsgTask, in, 0, NULL);
} else {
MessageBox(
hDlg,
loader.getString(IDS_SUCCESS_MSGSENT).c_str(),
loader.getString(IDS_SUCCESS).c_str(),
MB_ICONINFORMATION | MB_OK);
errtext = _T("Failed to allocate memory");
}
}
if (fail) {
MessageBox(hDlg, errtext.c_str(),
loader.getString(IDS_FAILED).c_str(),
MB_ICONERROR | MB_OK);
}
return DIALOG_OK;
}

Expand All @@ -76,6 +103,30 @@ INT_PTR CALLBACK SendMsgToChat(HWND hDlg, UINT message, WPARAM wParam,
return DIALOG_OK;
};
break;
case WM_SENDMSG_RESULT: {
if (lParam == NULL) {
return DIALOG_OK;
}
const auto* res = reinterpret_cast<SendMsgResult*>(lParam);
auto& loader = StringLoader::getInstance();

if (!res->success) {
StringLoader::String errtext;
errtext = loader.getString(IDS_CMD_FAILED_SVR) + kLineBreak;
errtext += loader.getString(IDS_CMD_FAILED_SVR_RSN);
errtext += charToWstring(res->reason);
MessageBox(hDlg, errtext.c_str(),
loader.getString(IDS_FAILED).c_str(),
ERROR_DIALOG);
} else {
MessageBox(hDlg, loader.getString(IDS_SUCCESS_MSGSENT).c_str(),
loader.getString(IDS_SUCCESS).c_str(),
INFO_DIALOG);
}
blk.stop();
free(reinterpret_cast<void*>(lParam));
return DIALOG_OK;
}
default:
break;
}
Expand Down
Binary file modified clientapp/windows/TgBotClient/TgBotClient.rc
Binary file not shown.
Loading

0 comments on commit 4c7fa0f

Please sign in to comment.