740 lines
25 KiB
C++
740 lines
25 KiB
C++
#include "webserverchat.h"
|
|
#include "qcolor.h"
|
|
#include <QCoreApplication>
|
|
#include <QDir>
|
|
#include <QDebug>
|
|
#include <QJsonDocument>
|
|
#include <QFileInfo>
|
|
#include <QDateTime>
|
|
|
|
HttpServerChat::HttpServerChat(const QStringList &fontList, int port, const QString &backgroundColor, QObject *parent)
|
|
: QObject(parent)
|
|
, m_server(nullptr)
|
|
, m_fontList(fontList)
|
|
, m_backgroundColor(backgroundColor)
|
|
, m_deleteByTime(true) // По умолчанию удаление по времени
|
|
, m_maxMsgCount(100) // Значение по умолчанию
|
|
{
|
|
m_server = new QTcpServer(this);
|
|
m_server->listen(QHostAddress::Any, port);
|
|
|
|
connect(m_server, &QTcpServer::newConnection, this, &HttpServerChat::onNewConnection);
|
|
m_currentStyle.freez = m_isFreez;
|
|
|
|
}
|
|
|
|
HttpServerChat::~HttpServerChat()
|
|
{
|
|
stop();
|
|
}
|
|
|
|
bool HttpServerChat::start()
|
|
{
|
|
if (m_server->isListening()) {
|
|
emit serverStarted(true);
|
|
return true;
|
|
}
|
|
|
|
if (!m_server->listen(QHostAddress::Any, m_server->serverPort())) {
|
|
|
|
emit serverStarted(false);
|
|
return false;
|
|
}
|
|
|
|
|
|
emit serverStarted(true);
|
|
return true;
|
|
}
|
|
|
|
void HttpServerChat::stop()
|
|
{
|
|
if (m_server) {
|
|
m_server->close();
|
|
}
|
|
|
|
for (auto client : m_clients) {
|
|
if (client->state() == QAbstractSocket::ConnectedState) {
|
|
client->close();
|
|
}
|
|
client->deleteLater();
|
|
}
|
|
m_clients.clear();
|
|
}
|
|
|
|
quint16 HttpServerChat::port() const
|
|
{
|
|
return m_server ? m_server->serverPort() : 0;
|
|
}
|
|
|
|
void HttpServerChat::addMessage(const StyleChat &style)
|
|
{
|
|
m_currentStyle = style;
|
|
|
|
ChatMessage msg;
|
|
msg.nickname = style.nick;
|
|
msg.content = style.context;
|
|
msg.timestamp = QDateTime::currentSecsSinceEpoch();
|
|
msg.timeMsg = style.timeMsg;
|
|
msg.freez = style.freez;
|
|
msg.transparency = style.transparency;
|
|
|
|
// Копируем стилевые параметры
|
|
msg.blockColor = style.blockColor;
|
|
msg.borderColor = style.borderColor;
|
|
msg.fontColor = style.fontColor;
|
|
msg.fontFamily = style.fontFamily;
|
|
msg.fontSize = style.fontSize;
|
|
msg.borderSize = style.borderSize;
|
|
msg.padding = style.padding;
|
|
|
|
if (!m_deleteByTime) {
|
|
// Режим удаления по количеству
|
|
// Проверяем, не превышен ли лимит
|
|
if (m_messages.size() >= m_maxMsgCount) {
|
|
// Ищем незамороженное сообщение для удаления
|
|
bool removed = false;
|
|
for (int i = 0; i < m_messages.size(); ++i) {
|
|
if (!m_messages[i].freez) {
|
|
m_messages.removeAt(i);
|
|
removed = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Если все сообщения заморожены и лимит превышен,
|
|
// не добавляем новое сообщение
|
|
if (!removed) {
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_messages.append(msg);
|
|
|
|
|
|
|
|
// Обновляем всех подключенных клиентов
|
|
for (auto client : m_clients) {
|
|
if (client->state() == QAbstractSocket::ConnectedState) {
|
|
// Отправляем JavaScript для обновления страницы
|
|
QString js = "<script>window.location.reload();</script>";
|
|
sendResponse(client, "text/html", js);
|
|
}
|
|
}
|
|
}
|
|
|
|
void HttpServerChat::activeServer(bool enabled)
|
|
{
|
|
if (enabled) {
|
|
if (!m_server->isListening()) {
|
|
m_server->listen(QHostAddress::Any, m_server->serverPort());
|
|
}
|
|
} else {
|
|
m_server->close();
|
|
}
|
|
}
|
|
|
|
|
|
void HttpServerChat::changeBackground(const QString &color)
|
|
{
|
|
m_backgroundColor = color;
|
|
}
|
|
|
|
|
|
void HttpServerChat::onNewConnection()
|
|
{
|
|
while (m_server->hasPendingConnections()) {
|
|
QTcpSocket *socket = m_server->nextPendingConnection();
|
|
m_clients.append(socket);
|
|
|
|
connect(socket, &QTcpSocket::readyRead, this, &HttpServerChat::readClient);
|
|
connect(socket, &QTcpSocket::disconnected, this, &HttpServerChat::discardClient);
|
|
|
|
|
|
}
|
|
}
|
|
|
|
void HttpServerChat::readClient()
|
|
{
|
|
QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
|
|
if (!socket) return;
|
|
|
|
QByteArray requestData = socket->readAll();
|
|
QString request = QString::fromUtf8(requestData);
|
|
|
|
if (!request.isEmpty()) {
|
|
processRequest(socket, request);
|
|
}
|
|
}
|
|
|
|
void HttpServerChat::processRequest(QTcpSocket *socket, const QString &request)
|
|
{
|
|
QStringList lines = request.split("\r\n");
|
|
if (lines.isEmpty()) return;
|
|
|
|
QString firstLine = lines[0];
|
|
QStringList parts = firstLine.split(" ");
|
|
if (parts.size() < 2) return;
|
|
|
|
QString method = parts[0];
|
|
QString path = parts[1];
|
|
|
|
|
|
|
|
|
|
cleanupOldMessages();
|
|
|
|
if (method == "GET") {
|
|
if (path == "/" || path == "/index.html") {
|
|
|
|
sendHtmlPage(socket);
|
|
} else if (path == "/messages") {
|
|
|
|
sendJSONMessages(socket);
|
|
} else if (path.startsWith("/fonts/")) {
|
|
QString filePath = path.mid(1); // Убираем первый слэш
|
|
|
|
serveStaticFile(socket, filePath);
|
|
} else {
|
|
// Игнорируем favicon и другие запросы
|
|
if (!path.contains("favicon.ico") && !path.contains(".well-known")) {
|
|
|
|
}
|
|
sendResponse(socket, "text/html", "", 404);
|
|
}
|
|
}
|
|
}
|
|
|
|
void HttpServerChat::sendHtmlPage(QTcpSocket *socket)
|
|
{
|
|
QString html = generateHTML();
|
|
sendResponse(socket, "text/html", html);
|
|
}
|
|
|
|
void HttpServerChat::sendJSONMessages(QTcpSocket *socket)
|
|
{
|
|
QString json = generateJSON();
|
|
sendResponse(socket, "application/json; charset=utf-8", json);
|
|
}
|
|
|
|
void HttpServerChat::serveStaticFile(QTcpSocket *socket, const QString &filePath)
|
|
{
|
|
QString fullPath = QCoreApplication::applicationDirPath() + "/" + filePath;
|
|
QFile file(fullPath);
|
|
|
|
|
|
|
|
if (file.exists() && file.open(QIODevice::ReadOnly)) {
|
|
QString mimeType = getMimeType(filePath);
|
|
QByteArray content = file.readAll();
|
|
file.close();
|
|
|
|
|
|
|
|
QString response = QString(
|
|
"HTTP/1.1 200 OK\r\n"
|
|
"Content-Type: %1\r\n"
|
|
"Content-Length: %2\r\n"
|
|
"Cache-Control: no-cache\r\n"
|
|
"Connection: close\r\n"
|
|
"\r\n")
|
|
.arg(mimeType)
|
|
.arg(content.size());
|
|
|
|
socket->write(response.toUtf8());
|
|
socket->write(content);
|
|
socket->flush();
|
|
} else {
|
|
|
|
sendResponse(socket, "text/html",
|
|
"<html><body><h1>404 Not Found</h1><p>" + filePath + "</p></body></html>",
|
|
404);
|
|
}
|
|
}
|
|
|
|
QString HttpServerChat::generateHTML()
|
|
{
|
|
// Генерация CSS для шрифтов
|
|
QString fontFaces;
|
|
for (const QString &fontFile : m_fontList) {
|
|
QString fontName = fontFile;
|
|
fontName.replace(".ttf", "").replace(".otf", "").replace(".ttc", "");
|
|
fontFaces += QString("@font-face { font-family: '%1'; src: url(fonts/%2); }\n")
|
|
.arg(fontName).arg(fontFile);
|
|
}
|
|
|
|
QString deleteByTimeJS = m_deleteByTime ? "true" : "false";
|
|
|
|
QString html =
|
|
"<!DOCTYPE html>\n"
|
|
"<html>\n"
|
|
"<head>\n"
|
|
"<meta charset='UTF-8'>\n"
|
|
"<meta http-equiv=\"Cache-Control\" content=\"no-cache, no-store, must-revalidate\">\n"
|
|
"<meta http-equiv=\"Pragma\" content=\"no-cache\">\n"
|
|
"<meta http-equiv=\"Expires\" content=\"0\">\n"
|
|
"<title>Chat Messages</title>\n"
|
|
"<style>\n"
|
|
"body { background: %1; margin: 0; padding: 0; overflow: hidden; }\n"
|
|
"#messages { \n"
|
|
" position: fixed; \n"
|
|
" bottom: 20px; \n"
|
|
" right: 20px; \n"
|
|
" width: 400px; \n"
|
|
" display: flex; \n"
|
|
" flex-direction: column; \n"
|
|
" align-items: flex-end; \n"
|
|
"}\n"
|
|
".message { \n"
|
|
" margin: 3px 0; \n"
|
|
" border-radius: 5px; \n"
|
|
" transition: opacity 0.5s linear; \n"
|
|
" display: flex; \n"
|
|
" align-items: center; \n"
|
|
" opacity: 1;\n"
|
|
" max-width: 100%;\n"
|
|
" word-wrap: break-word;\n"
|
|
"}\n"
|
|
".message-icon { \n"
|
|
" width: 1.5em; \n"
|
|
" height: 1.5em; \n"
|
|
" margin-right: 0.5em; \n"
|
|
"}\n"
|
|
"%2\n" // font faces
|
|
"</style>\n"
|
|
"<script>\n"
|
|
"let existingMessages = new Map();\n"
|
|
"let fetching = false;\n"
|
|
"const deleteByTime = %3;\n"
|
|
"\n"
|
|
"function hexToRgb(hex) {\n"
|
|
" console.log('hexToRgb input:', hex);\n"
|
|
" \n"
|
|
" // Создаем временный canvas элемент для парсинга цвета\n"
|
|
" const ctx = document.createElement('canvas').getContext('2d');\n"
|
|
" ctx.fillStyle = hex;\n"
|
|
" const color = ctx.fillStyle;\n"
|
|
" \n"
|
|
" // Если цвет не распознан, используем значение по умолчанию\n"
|
|
" if (!color || color === 'rgba(0, 0, 0, 0)') {\n"
|
|
" console.log('Color not recognized, using default');\n"
|
|
" return '74, 175, 80'; // #4CAF50\n"
|
|
" }\n"
|
|
" \n"
|
|
" // Парсим цвет в формате rgb(r, g, b)\n"
|
|
" const match = color.match(/rgb\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)/);\n"
|
|
" \n"
|
|
" if (match) {\n"
|
|
" console.log('Parsed RGB:', match[1], match[2], match[3]);\n"
|
|
" return `${match[1]}, ${match[2]}, ${match[3]}`;\n"
|
|
" }\n"
|
|
" \n"
|
|
" console.log('Failed to parse, using default');\n"
|
|
" return '74, 175, 80'; // #4CAF50\n"
|
|
"}\n"
|
|
"\n"
|
|
"function fetchMessages() {\n"
|
|
" if (fetching) return;\n"
|
|
" fetching = true;\n"
|
|
" \n"
|
|
" fetch('/messages')\n"
|
|
" .then(response => response.json())\n"
|
|
" .then(data => {\n"
|
|
" const container = document.getElementById('messages');\n"
|
|
" \n"
|
|
" // 1. СОЗДАЕМ СЕТ ИЗ ВСЕХ ИМЕЮЩИХСЯ ID\n"
|
|
" const newIds = new Set();\n"
|
|
" data.forEach(msg => {\n"
|
|
" const msgId = 'msg-' + msg.timestamp;\n"
|
|
" newIds.add(msgId);\n"
|
|
" });\n"
|
|
" \n"
|
|
" // 2. УДАЛЯЕМ ТОЛЬКО ТЕ СООБЩЕНИЯ, КОТОРЫХ НЕТ В НОВЫХ ДАННЫХ\n"
|
|
" // И КОТОРЫЕ НЕ ЗАМОРОЖЕНЫ\n"
|
|
" existingMessages.forEach((div, msgId) => {\n"
|
|
" if (!newIds.has(msgId)) {\n"
|
|
" // Проверяем, заморожено ли сообщение\n"
|
|
" const isFreez = div.getAttribute('data-freez') === 'true';\n"
|
|
" if (!isFreez) {\n"
|
|
" div.style.opacity = '0';\n"
|
|
" setTimeout(() => {\n"
|
|
" if (div.parentNode) {\n"
|
|
" div.parentNode.removeChild(div);\n"
|
|
" }\n"
|
|
" existingMessages.delete(msgId);\n"
|
|
" }, 500);\n"
|
|
" }\n"
|
|
" }\n"
|
|
" });\n"
|
|
" \n"
|
|
" // 3. ДОБАВЛЯЕМ НОВЫЕ СООБЩЕНИЯ\n"
|
|
" data.forEach(msg => {\n"
|
|
" const msgId = 'msg-' + msg.timestamp;\n"
|
|
" \n"
|
|
" if (!existingMessages.has(msgId)) {\n"
|
|
" const div = document.createElement('div');\n"
|
|
" div.className = 'message';\n"
|
|
" div.id = msgId;\n"
|
|
" div.setAttribute('data-freez', msg.freez ? 'true' : 'false');\n"
|
|
" \n"
|
|
" // ПРИМЕНЯЕМ СТИЛИ\n"
|
|
" function hexToRgb(hex) {\n"
|
|
" console.log('hexToRgb input:', hex);\n"
|
|
" if (!hex || !hex.startsWith('#')) {\n"
|
|
" console.log('Not a hex color, using default');\n"
|
|
" return '74, 175, 80'; // #4CAF50\n"
|
|
" }\n"
|
|
" // Убираем # и проверяем длину\n"
|
|
" let cleanHex = hex.substring(1);\n"
|
|
" // Убираем альфа-канал если есть\n"
|
|
" if (cleanHex.length === 8) {\n"
|
|
" cleanHex = cleanHex.substring(0, 6);\n"
|
|
" }\n"
|
|
" // Расширяем короткую форму (#rgb → #rrggbb)\n"
|
|
" if (cleanHex.length === 3) {\n"
|
|
" cleanHex = cleanHex[0] + cleanHex[0] + \n"
|
|
" cleanHex[1] + cleanHex[1] + \n"
|
|
" cleanHex[2] + cleanHex[2];\n"
|
|
" }\n"
|
|
" // Проверяем, что строка состоит из шестнадцатеричных символов\n"
|
|
" if (cleanHex.length !== 6 || !/^[0-9A-Fa-f]{6}$/.test(cleanHex)) {\n"
|
|
" console.log('Invalid hex format, using default');\n"
|
|
" return '74, 175, 80'; // #4CAF50\n"
|
|
" }\n"
|
|
" // Конвертируем HEX в RGB\n"
|
|
" const r = parseInt(cleanHex.substring(0, 2), 16);\n"
|
|
" const g = parseInt(cleanHex.substring(2, 4), 16);\n"
|
|
" const b = parseInt(cleanHex.substring(4, 6), 16);\n"
|
|
" console.log('Parsed RGB:', r, g, b);\n"
|
|
" return `${r}, ${g}, ${b}`;\n"
|
|
" }\n"
|
|
" \n"
|
|
" // ФОРМИРУЕМ СОДЕРЖИМОЕ\n"
|
|
" let content = '<span><b>' + msg.nickname + ':</b> ' + msg.content + '</span>';\n"
|
|
" div.innerHTML = content;\n"
|
|
" div.style.opacity = '1';\n"
|
|
" \n"
|
|
" container.appendChild(div);\n"
|
|
" existingMessages.set(msgId, div);\n"
|
|
" \n"
|
|
" // 4. УСТАНАВЛИВАЕМ ТАЙМЕР УДАЛЕНИЯ ТОЛЬКО ЕСЛИ:\n"
|
|
" // - включено удаление по времени (deleteByTime)\n"
|
|
" // - сообщение НЕ заморожено (freez = false)\n"
|
|
" if (deleteByTime && (!msg.freez || msg.freez === false)) {\n"
|
|
" setTimeout(() => {\n"
|
|
" if (existingMessages.has(msgId)) {\n"
|
|
" const messageDiv = existingMessages.get(msgId);\n"
|
|
" messageDiv.style.opacity = '0';\n"
|
|
" setTimeout(() => {\n"
|
|
" if (messageDiv.parentNode) {\n"
|
|
" messageDiv.parentNode.removeChild(messageDiv);\n"
|
|
" }\n"
|
|
" existingMessages.delete(msgId);\n"
|
|
" }, 500);\n"
|
|
" }\n"
|
|
" }, (parseInt(msg.timeMsg) || 10) * 1000);\n"
|
|
" }\n"
|
|
" }\n"
|
|
" });\n"
|
|
" \n"
|
|
" // 5. ОБРАБОТКА РЕЖИМА УДАЛЕНИЯ ПО КОЛИЧЕСТВУ\n"
|
|
" if (!deleteByTime) {\n"
|
|
" const maxMessages = 100;\n"
|
|
" if (existingMessages.size > maxMessages) {\n"
|
|
" const messagesArray = Array.from(existingMessages.entries());\n"
|
|
" messagesArray.sort((a, b) => {\n"
|
|
" const idA = parseInt(a[0].replace('msg-', ''));\n"
|
|
" const idB = parseInt(b[0].replace('msg-', ''));\n"
|
|
" return idA - idB;\n"
|
|
" });\n"
|
|
" \n"
|
|
" let removedCount = 0;\n"
|
|
" for (const [msgId, div] of messagesArray) {\n"
|
|
" if (existingMessages.size - removedCount <= maxMessages) break;\n"
|
|
" \n"
|
|
" const isFreez = div.getAttribute('data-freez') === 'true';\n"
|
|
" if (!isFreez) {\n"
|
|
" div.style.opacity = '0';\n"
|
|
" setTimeout(() => {\n"
|
|
" if (div.parentNode) {\n"
|
|
" div.parentNode.removeChild(div);\n"
|
|
" }\n"
|
|
" existingMessages.delete(msgId);\n"
|
|
" }, 500);\n"
|
|
" removedCount++;\n"
|
|
" }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" })\n"
|
|
" .catch(error => console.error('Ошибка при загрузке сообщений:', error))\n"
|
|
" .finally(() => { fetching = false; });\n"
|
|
"}\n"
|
|
"\n"
|
|
"// Обновляем сообщения каждые 500 мс\n"
|
|
"setInterval(fetchMessages, 500);\n"
|
|
"\n"
|
|
"// Первоначальная загрузка\n"
|
|
"fetchMessages();\n"
|
|
"</script>\n"
|
|
"</head>\n"
|
|
"<body>\n"
|
|
"<div id='messages'></div>\n"
|
|
"</body>\n"
|
|
"</html>";
|
|
|
|
return html.arg(m_backgroundColor).arg(fontFaces).arg(deleteByTimeJS);
|
|
}
|
|
|
|
QString HttpServerChat::generateJSON()
|
|
{
|
|
QJsonArray jsonArray;
|
|
|
|
for (const ChatMessage &msg : m_messages) {
|
|
QJsonObject obj;
|
|
obj["nickname"] = msg.nickname;
|
|
obj["content"] = msg.content;
|
|
obj["timestamp"] = msg.timestamp;
|
|
obj["timeMsg"] = msg.timeMsg;
|
|
obj["freez"] = msg.freez;
|
|
obj["transparency"] = msg.transparency;
|
|
|
|
// Конвертируем цветовые названия в HEX формат
|
|
QString blockColor = msg.blockColor.isEmpty() ? m_currentStyle.blockColor : msg.blockColor;
|
|
QString borderColor = msg.borderColor.isEmpty() ? m_currentStyle.borderColor : msg.borderColor;
|
|
QString fontColor = msg.fontColor.isEmpty() ? m_currentStyle.fontColor : msg.fontColor;
|
|
// Конвертируем названия цветов в HEX формат
|
|
obj["color"] = colorNameToHex(blockColor);
|
|
obj["borderColor"] = colorNameToHex(borderColor);
|
|
obj["fontColor"] = colorNameToHex(fontColor);
|
|
|
|
obj["family"] = msg.fontFamily.isEmpty() ? m_currentStyle.fontFamily : msg.fontFamily;
|
|
obj["fontSize"] = msg.fontSize > 0 ? msg.fontSize : m_currentStyle.fontSize;
|
|
obj["borderSize"] = msg.borderSize > 0 ? msg.borderSize : m_currentStyle.borderSize;
|
|
obj["padding"] = msg.padding > 0 ? msg.padding : m_currentStyle.padding;
|
|
|
|
jsonArray.append(obj);
|
|
}
|
|
|
|
QJsonDocument doc(jsonArray);
|
|
return QString::fromUtf8(doc.toJson());
|
|
}
|
|
|
|
QString HttpServerChat::colorNameToHex(const QString &colorName) const
|
|
{
|
|
if (colorName.isEmpty()) {
|
|
return "#000000";
|
|
}
|
|
|
|
// Если строка уже начинается с #, это HEX формат
|
|
if (colorName.startsWith('#')) {
|
|
return colorName;
|
|
}
|
|
|
|
// Проверяем, является ли это названием цвета
|
|
QColor color;
|
|
if (color.isValidColor(colorName)) {
|
|
color.setNamedColor(colorName);
|
|
return color.name();
|
|
}
|
|
|
|
// Пробуем создать QColor из строки
|
|
color = QColor(colorName);
|
|
if (color.isValid()) {
|
|
return color.name();
|
|
}
|
|
|
|
// Если ничего не получилось, возвращаем черный по умолчанию
|
|
return "#000000";
|
|
}
|
|
|
|
void HttpServerChat::sendResponse(QTcpSocket *socket, const QString &contentType, const QString &content, int statusCode)
|
|
{
|
|
QString statusText;
|
|
switch(statusCode) {
|
|
case 200: statusText = "OK"; break;
|
|
case 404: statusText = "Not Found"; break;
|
|
default: statusText = "OK"; break;
|
|
}
|
|
|
|
QByteArray responseData = QString(
|
|
"HTTP/1.1 %1 %2\r\n"
|
|
"Content-Type: %3\r\n"
|
|
"Content-Length: %4\r\n"
|
|
"Cache-Control: no-cache, no-store, must-revalidate\r\n"
|
|
"Access-Control-Allow-Origin: *\r\n"
|
|
"Connection: keep-alive\r\n"
|
|
"\r\n"
|
|
"%5")
|
|
.arg(statusCode)
|
|
.arg(statusText)
|
|
.arg(contentType)
|
|
.arg(content.toUtf8().size())
|
|
.arg(content)
|
|
.toUtf8();
|
|
|
|
socket->write(responseData);
|
|
socket->flush();
|
|
}
|
|
|
|
QString HttpServerChat::getMimeType(const QString &filePath)
|
|
{
|
|
if (filePath.endsWith(".html")) return "text/html";
|
|
if (filePath.endsWith(".css")) return "text/css";
|
|
if (filePath.endsWith(".js")) return "application/javascript";
|
|
if (filePath.endsWith(".png")) return "image/png";
|
|
if (filePath.endsWith(".jpg") || filePath.endsWith(".jpeg")) return "image/jpeg";
|
|
if (filePath.endsWith(".gif")) return "image/gif";
|
|
if (filePath.endsWith(".svg")) return "image/svg+xml";
|
|
if (filePath.endsWith(".ico")) return "image/x-icon";
|
|
if (filePath.endsWith(".ttf")) return "font/ttf";
|
|
if (filePath.endsWith(".otf")) return "font/otf";
|
|
if (filePath.endsWith(".woff")) return "font/woff";
|
|
if (filePath.endsWith(".woff2")) return "font/woff2";
|
|
return "text/plain";
|
|
}
|
|
|
|
void HttpServerChat::discardClient()
|
|
{
|
|
QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
|
|
if (socket) {
|
|
m_clients.removeOne(socket);
|
|
socket->deleteLater();
|
|
|
|
}
|
|
}
|
|
|
|
// Реализация методов получения настроек
|
|
QString HttpServerChat::getFontFamily() const {
|
|
return m_currentStyle.fontFamily;
|
|
}
|
|
|
|
int HttpServerChat::getFontSize() const {
|
|
return m_currentStyle.fontSize;
|
|
}
|
|
|
|
QString HttpServerChat::getFontColor() const {
|
|
return m_currentStyle.fontColor;
|
|
}
|
|
|
|
QString HttpServerChat::getBlockColor() const {
|
|
return m_currentStyle.blockColor;
|
|
}
|
|
|
|
QString HttpServerChat::getBorderColor() const {
|
|
return m_currentStyle.borderColor;
|
|
}
|
|
|
|
int HttpServerChat::getBorderSize() const {
|
|
return m_currentStyle.borderSize;
|
|
}
|
|
|
|
int HttpServerChat::getPadding() const {
|
|
return m_currentStyle.padding;
|
|
}
|
|
|
|
int HttpServerChat::getMessageTimeout() const {
|
|
return m_currentStyle.timeMsg;
|
|
}
|
|
|
|
QString HttpServerChat::getBackgroundColor() const {
|
|
return m_backgroundColor;
|
|
}
|
|
|
|
int HttpServerChat::getTransparency() const {
|
|
return m_currentStyle.transparency;
|
|
}
|
|
|
|
|
|
void HttpServerChat::setFontFamily(const QString &fontFamily) {
|
|
m_currentStyle.fontFamily = fontFamily;
|
|
}
|
|
|
|
void HttpServerChat::setFontSize(int fontSize) {
|
|
m_currentStyle.fontSize = fontSize;
|
|
}
|
|
|
|
void HttpServerChat::setFontColor(const QString &fontColor) {
|
|
m_currentStyle.fontColor = fontColor;
|
|
}
|
|
|
|
void HttpServerChat::setBlockColor(const QString &blockColor) {
|
|
m_currentStyle.blockColor = blockColor;
|
|
}
|
|
|
|
void HttpServerChat::setBorderColor(const QString &borderColor) {
|
|
m_currentStyle.borderColor = borderColor;
|
|
}
|
|
|
|
void HttpServerChat::setBorderSize(int borderSize) {
|
|
m_currentStyle.borderSize = borderSize;
|
|
}
|
|
|
|
void HttpServerChat::setPadding(int padding) {
|
|
m_currentStyle.padding = padding;
|
|
}
|
|
|
|
void HttpServerChat::setMessageTimeout(int timeout) {
|
|
m_currentStyle.timeMsg = timeout;
|
|
}
|
|
|
|
void HttpServerChat::setTransparency(int transparency) {
|
|
m_currentStyle.transparency = qBound(0, transparency, 255);
|
|
}
|
|
|
|
void HttpServerChat::setDeleteMode(bool deleteByTime, int maxMsgCount)
|
|
{
|
|
m_deleteByTime = deleteByTime;
|
|
m_maxMsgCount = maxMsgCount;
|
|
|
|
if (!deleteByTime) {
|
|
// Режим удаления по количеству
|
|
// Удаляем самые старые сообщения, НО сохраняем замороженные
|
|
while (m_messages.size() > m_maxMsgCount) {
|
|
// Ищем незамороженное сообщение для удаления
|
|
bool removed = false;
|
|
for (int i = 0; i < m_messages.size(); ++i) {
|
|
if (!m_messages[i].freez) {
|
|
m_messages.removeAt(i);
|
|
removed = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Если все сообщения заморожены, выходим из цикла
|
|
if (!removed) {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
// Режим удаления по времени
|
|
// Замороженные сообщения сохраняются в любом случае
|
|
cleanupOldMessages();
|
|
}
|
|
}
|
|
|
|
void HttpServerChat::cleanupOldMessages()
|
|
{
|
|
if (!m_deleteByTime) {
|
|
return; // Если удаление по времени отключено, выходим
|
|
}
|
|
|
|
qint64 currentTime = QDateTime::currentSecsSinceEpoch();
|
|
|
|
// Проходим сообщения в обратном порядке для безопасного удаления
|
|
for (int i = m_messages.size() - 1; i >= 0; --i) {
|
|
const ChatMessage &msg = m_messages[i];
|
|
|
|
// Пропускаем замороженные сообщения
|
|
if (msg.freez) {
|
|
continue;
|
|
}
|
|
|
|
qint64 messageAge = currentTime - msg.timestamp;
|
|
if (messageAge >= msg.timeMsg) {
|
|
m_messages.removeAt(i);
|
|
}
|
|
}
|
|
}
|