refactor
This commit is contained in:
+12
@@ -21,15 +21,21 @@ RC_ICONS = ico\app_icon.ico
|
||||
INCLUDEPATH += $$PWD
|
||||
|
||||
SOURCES += \
|
||||
commandprocessor.cpp \
|
||||
emoteprovider.cpp \
|
||||
fcolorsetting.cpp \
|
||||
fcreatechat.cpp \
|
||||
fcreatenotify.cpp \
|
||||
ffontsetting.cpp \
|
||||
filemanager.cpp \
|
||||
fsettingsws.cpp \
|
||||
fsinglegrid.cpp \
|
||||
logmanager.cpp \
|
||||
main.cpp \
|
||||
mediafilemanager.cpp \
|
||||
neuralnetworkmanager.cpp \
|
||||
randommanager.cpp \
|
||||
randomresponses.cpp \
|
||||
soundmanager.cpp \
|
||||
tauth.cpp \
|
||||
ttw_api.cpp \
|
||||
@@ -46,15 +52,21 @@ SOURCES += \
|
||||
|
||||
HEADERS += \
|
||||
badge.h \
|
||||
commandprocessor.h \
|
||||
emoteprovider.h \
|
||||
fcolorsetting.h \
|
||||
fcreatechat.h \
|
||||
fcreatenotify.h \
|
||||
ffontsetting.h \
|
||||
filemanager.h \
|
||||
fsettingsws.h \
|
||||
fsinglegrid.h \
|
||||
logmanager.h \
|
||||
mediafilemanager.h \
|
||||
miniaudio.h \
|
||||
neuralnetworkmanager.h \
|
||||
randommanager.h \
|
||||
randomresponses.h \
|
||||
soundmanager.h \
|
||||
tauth.h \
|
||||
timerinfo.h \
|
||||
|
||||
@@ -0,0 +1,476 @@
|
||||
#include "commandprocessor.h"
|
||||
#include <QRegularExpression>
|
||||
#include <QRandomGenerator>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QEventLoop>
|
||||
#include <QTimer>
|
||||
|
||||
CommandProcessor::CommandProcessor(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void CommandProcessor::setContext(const Context& context)
|
||||
{
|
||||
m_context = context;
|
||||
}
|
||||
|
||||
QString CommandProcessor::generateResponse(QString userIndex, const QString &command, const QString &message)
|
||||
{
|
||||
qDebug() << "generateResponse: userIndex =" << userIndex << "command =" << command;
|
||||
|
||||
// Сначала пробуем найти пользователя по displayName
|
||||
QString username = "";
|
||||
if (m_context.userManager) {
|
||||
User* user = m_context.userManager->findUser(userIndex);
|
||||
if (user) {
|
||||
username = user->displayName;
|
||||
qDebug() << "Найден пользователь:" << username;
|
||||
} else {
|
||||
qDebug() << "Пользователь не найден в UserManager по displayName:" << userIndex;
|
||||
|
||||
// Попробуем найти по ID
|
||||
user = m_context.userManager->findUserById(userIndex);
|
||||
if (user) {
|
||||
username = user->displayName;
|
||||
qDebug() << "Найден пользователь по ID:" << username;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (username.isEmpty()) {
|
||||
// Если не нашли в UserManager, используем переданное имя
|
||||
username = userIndex;
|
||||
qDebug() << "Используем переданное имя:" << username;
|
||||
}
|
||||
|
||||
Command cmd = findCommand(command);
|
||||
if (cmd.command.isEmpty()) {
|
||||
qDebug() << "Команда не найдена:" << command;
|
||||
return QString("Команда '%1' не найдена").arg(command);
|
||||
}
|
||||
|
||||
qDebug() << "Найдена команда:" << cmd.command << "ответ:" << cmd.response;
|
||||
|
||||
QString fullCommand = command + (message.isEmpty() ? "" : " " + message);
|
||||
QString result = processCommand(username, fullCommand, cmd.response);
|
||||
|
||||
qDebug() << "Итоговый результат:" << result;
|
||||
return result;
|
||||
}
|
||||
|
||||
void CommandProcessor::addCommand(const QString &command, const QString &response)
|
||||
{
|
||||
m_commands.append({command, response});
|
||||
}
|
||||
|
||||
void CommandProcessor::addCommands(const QVector<Command> &commands)
|
||||
{
|
||||
m_commands.append(commands);
|
||||
}
|
||||
|
||||
void CommandProcessor::clearCommands()
|
||||
{
|
||||
m_commands.clear();
|
||||
}
|
||||
|
||||
CommandProcessor::Command CommandProcessor::findCommand(const QString &commandName) const
|
||||
{
|
||||
for (const Command &cmd : m_commands) {
|
||||
if (cmd.command.compare(commandName, Qt::CaseInsensitive) == 0) {
|
||||
return cmd;
|
||||
}
|
||||
}
|
||||
return Command();
|
||||
}
|
||||
|
||||
QString CommandProcessor::getUsernameByIndex(QString userIndex) const
|
||||
{
|
||||
if (!m_context.userManager) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
const User* user = m_context.userManager->getUserByIndex(userIndex);
|
||||
return user ? user->displayName : QString();
|
||||
}
|
||||
|
||||
QString CommandProcessor::processCommand(const QString &sender, const QString &fullCommand, const QString &rawResponse)
|
||||
{
|
||||
QString response = rawResponse;
|
||||
QString parameters = extractParameters(fullCommand);
|
||||
|
||||
response = parseStatic(response, sender, parameters);
|
||||
// Рекурсивная обработка случайных групп (до 5 уровней вложенности)
|
||||
for (int i = 0; i < 5; i++) {
|
||||
QString before = response;
|
||||
response = parseRandomGroups(response);
|
||||
|
||||
// Если после обработки строка не изменилась, значит групп больше нет
|
||||
if (before == response) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
response = parseRandomNumbers(response); // Затем обрабатываем случайные числа ВНУТРИ них
|
||||
response = parseSounds(response);
|
||||
response = parseTextFiles(response);
|
||||
response = parseBan(response, sender);
|
||||
response = parseAPI(response, sender);
|
||||
|
||||
if (response.contains("[AI]", Qt::CaseInsensitive)) {
|
||||
response = parseAI(response, parameters);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
QString CommandProcessor::extractParameters(const QString &fullCommand)
|
||||
{
|
||||
int spacePos = fullCommand.indexOf(' ');
|
||||
if (spacePos != -1) {
|
||||
return fullCommand.mid(spacePos + 1).trimmed();
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString CommandProcessor::parseStatic(const QString &response, const QString &sender, const QString ¶meters)
|
||||
{
|
||||
QString res = response;
|
||||
|
||||
res = res.replace("[USERNAME]", "@" + sender);
|
||||
res = res.replace("[TO]", parameters);
|
||||
|
||||
if (res.contains("[RANDOMUSER]")) {
|
||||
QString randomUserName;
|
||||
if (parameters.contains('@')) {
|
||||
randomUserName = parameters;
|
||||
} else if (m_context.userManager) {
|
||||
const User* randomUser = m_context.userManager->getRandomUser();
|
||||
if (randomUser) {
|
||||
randomUserName = "@" + randomUser->displayName;
|
||||
} else {
|
||||
randomUserName = "@viewer";
|
||||
}
|
||||
} else {
|
||||
randomUserName = "@viewer";
|
||||
}
|
||||
res = res.replace("[RANDOMUSER]", randomUserName);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
QString CommandProcessor::parseRandomNumbers(const QString &response)
|
||||
{
|
||||
QString result = response;
|
||||
QRegularExpression regex("\\[\\[([^\\]]+)\\]\\]");
|
||||
QRegularExpressionMatchIterator matches = regex.globalMatch(response);
|
||||
|
||||
qDebug() << "parseRandomNumbers: исходная строка:" << response;
|
||||
qDebug() << "Найдено совпадений:" << matches.hasNext();
|
||||
|
||||
while (matches.hasNext()) {
|
||||
QRegularExpressionMatch match = matches.next();
|
||||
QString rangeName = match.captured(1);
|
||||
qDebug() << "Найден диапазон:" << rangeName;
|
||||
|
||||
if (m_context.randomManager) {
|
||||
int randomNumber = m_context.randomManager->getRandomValue(rangeName);
|
||||
qDebug() << "Получено случайное число:" << randomNumber << "для диапазона" << rangeName;
|
||||
result.replace("[[" + rangeName + "]]", QString::number(randomNumber));
|
||||
} else {
|
||||
qDebug() << "RandomManager не инициализирован!";
|
||||
int fallbackNumber = QRandomGenerator::global()->bounded(1, 101);
|
||||
result.replace("[[" + rangeName + "]]", QString::number(fallbackNumber));
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Результат после замены:" << result;
|
||||
return result;
|
||||
}
|
||||
|
||||
QString CommandProcessor::parseSounds(const QString &response)
|
||||
{
|
||||
QString result = response;
|
||||
QRegularExpression regex("\\|\\|([^\\|]+)\\|\\|");
|
||||
QRegularExpressionMatchIterator matches = regex.globalMatch(response);
|
||||
|
||||
while (matches.hasNext()) {
|
||||
QRegularExpressionMatch match = matches.next();
|
||||
QString soundCommand = match.captured(1);
|
||||
QString fn = m_context.mediaFileManager->getFilePathByName(soundCommand);
|
||||
|
||||
if (m_context.soundManager) {
|
||||
m_context.soundManager->loadSound(SoundManager::SoundChannel::Channel1, fn);
|
||||
m_context.soundManager->playSound(SoundManager::SoundChannel::Channel1);
|
||||
}
|
||||
|
||||
result.replace("||" + soundCommand + "||", "");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString CommandProcessor::parseTextFiles(const QString &response)
|
||||
{
|
||||
QString result = response;
|
||||
QRegularExpression regex("\\|\\(([^\\)]+)\\|\\(");
|
||||
QRegularExpressionMatchIterator matches = regex.globalMatch(response);
|
||||
|
||||
while (matches.hasNext()) {
|
||||
QRegularExpressionMatch match = matches.next();
|
||||
QString fileName = match.captured(1);
|
||||
|
||||
QString content = getTextFileContent(fileName);
|
||||
if (!content.isEmpty()) {
|
||||
result.replace("|(" + fileName + "|(", content);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString CommandProcessor::parseRandomGroups(const QString &response)
|
||||
{
|
||||
QString result = response;
|
||||
QRegularExpression regex("\\{\\{([^\\}]+)\\}\\}");
|
||||
QRegularExpressionMatchIterator matches = regex.globalMatch(response);
|
||||
|
||||
while (matches.hasNext()) {
|
||||
QRegularExpressionMatch match = matches.next();
|
||||
QString groupName = match.captured(1);
|
||||
|
||||
QString randomResponse = getRandomResponseFromGroup(groupName);
|
||||
if (!randomResponse.isEmpty()) {
|
||||
result.replace("{{" + groupName + "}}", randomResponse);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString CommandProcessor::parseBan(const QString &response, const QString &sender)
|
||||
{
|
||||
QString result = response;
|
||||
QRegularExpression regex("\\[BAN(\\d*)\\]");
|
||||
QRegularExpressionMatchIterator matches = regex.globalMatch(response);
|
||||
|
||||
while (matches.hasNext()) {
|
||||
QRegularExpressionMatch match = matches.next();
|
||||
QString banTimeStr = match.captured(1);
|
||||
int banSeconds = banTimeStr.isEmpty() ? 0 : banTimeStr.toInt();
|
||||
if (m_context.twitchAPI) {
|
||||
if (m_context.userManager) {
|
||||
User* user = m_context.userManager->findUser(sender);
|
||||
qDebug() << user->displayName;
|
||||
if (user && !user->id.isEmpty()) {
|
||||
if (banSeconds > 0) {
|
||||
m_context.twitchAPI->banUserTime(user->id, banSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.replace("[BAN" + banTimeStr + "]", "");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString CommandProcessor::parseAPI(const QString &response, const QString &sender)
|
||||
{
|
||||
QString result = response;
|
||||
|
||||
if (result.contains("[FOLLOW]", Qt::CaseInsensitive)) {
|
||||
if (m_context.userManager && m_context.twitchAPI) {
|
||||
User* user = m_context.userManager->findUser(sender);
|
||||
if (user) {
|
||||
if (user->id.isEmpty() && !user->login.isEmpty()) {
|
||||
User fullUser = m_context.twitchAPI->getUserByLogin(user->login);
|
||||
user->id = fullUser.id;
|
||||
}
|
||||
|
||||
if (!user->id.isEmpty()) {
|
||||
QDate followDate = m_context.twitchAPI->getFollow(user->id);
|
||||
if (followDate.isValid()) {
|
||||
QString follow = getDateDifferenceString(followDate);
|
||||
result.replace("[FOLLOW]", follow, Qt::CaseInsensitive);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result.contains("[FOLLOW]", Qt::CaseInsensitive)) {
|
||||
result.replace("[FOLLOW]", "неизвестно", Qt::CaseInsensitive);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.contains("[AGE]", Qt::CaseInsensitive)) {
|
||||
if (m_context.userManager && m_context.twitchAPI) {
|
||||
User* user = m_context.userManager->findUser(sender);
|
||||
if (user && !user->login.isEmpty()) {
|
||||
User fullUser = m_context.twitchAPI->getUserByLogin(user->login);
|
||||
if (fullUser.createdAt.isValid()) {
|
||||
QString age = getDateDifferenceString(fullUser.createdAt);
|
||||
result.replace("[AGE]", age, Qt::CaseInsensitive);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result.contains("[AGE]", Qt::CaseInsensitive)) {
|
||||
result.replace("[AGE]", "неизвестно", Qt::CaseInsensitive);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.contains("[STAT]", Qt::CaseInsensitive)) {
|
||||
if (m_context.twitchAPI && !m_context.channel.isEmpty()) {
|
||||
int avgViewers = 0, maxViewers = 0, hoursWatched = 0, followers = 0, followersTotal = 0;
|
||||
m_context.twitchAPI->getTTWStat(m_context.channel, avgViewers, maxViewers,
|
||||
hoursWatched, followers, followersTotal);
|
||||
|
||||
QString stat = QString("Средний онлайн: %1; Максимальный онлайн: %2; "
|
||||
"Часов просмотра: %3; Подписчиков за месяц: %4; "
|
||||
"Всего подписчиков: %5")
|
||||
.arg(avgViewers).arg(maxViewers).arg(hoursWatched)
|
||||
.arg(followers).arg(followersTotal);
|
||||
result.replace("[STAT]", stat, Qt::CaseInsensitive);
|
||||
} else {
|
||||
result.replace("[STAT]", "Статистика недоступна", Qt::CaseInsensitive);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString CommandProcessor::parseAI(const QString &response, const QString &question)
|
||||
{
|
||||
QString res = response;
|
||||
if (!m_context.neuralManager) {
|
||||
return res.replace("[AI]", "Нейросеть недоступна", Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
if (question.isEmpty()) {
|
||||
return res.replace("[AI]", "Ошибка: не указан вопрос для нейросети", Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
QString aiResponse;
|
||||
bool responseReceived = false;
|
||||
bool errorOccurred = false;
|
||||
QString errorMessage;
|
||||
|
||||
QEventLoop eventLoop;
|
||||
|
||||
auto conn1 = connect(m_context.neuralManager, &NeuralNetworkManager::responseReceived,
|
||||
[&](const QString &response1) {
|
||||
aiResponse = response1;
|
||||
responseReceived = true;
|
||||
eventLoop.quit();
|
||||
});
|
||||
|
||||
auto conn2 = connect(m_context.neuralManager, &NeuralNetworkManager::errorOccurred,
|
||||
[&](const QString &error) {
|
||||
errorMessage = error;
|
||||
errorOccurred = true;
|
||||
eventLoop.quit();
|
||||
});
|
||||
|
||||
m_context.neuralManager->sendMessage(question, NeuralNetworkManager::DeepSeek);
|
||||
|
||||
QTimer::singleShot(60000, &eventLoop, &QEventLoop::quit);
|
||||
eventLoop.exec();
|
||||
|
||||
disconnect(conn1);
|
||||
disconnect(conn2);
|
||||
|
||||
if (errorOccurred) {
|
||||
return res.replace("[AI]",
|
||||
QString("Ошибка нейросети: %1").arg(errorMessage),
|
||||
Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
if (!responseReceived) {
|
||||
return res.replace("[AI]",
|
||||
"Таймаут при ожидании ответа от нейросети",
|
||||
Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
return res.replace("[AI]", aiResponse, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
QString CommandProcessor::getRandomResponseFromGroup(const QString &groupName)
|
||||
{
|
||||
if (m_context.randomResponses) {
|
||||
return m_context.randomResponses->getResponse(groupName);
|
||||
}
|
||||
return QString("Ответ из группы: " + groupName);
|
||||
}
|
||||
|
||||
QString CommandProcessor::getTextFileContent(const QString &fileName)
|
||||
{
|
||||
QFile file(fileName);
|
||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
QTextStream stream(&file);
|
||||
stream.setCodec("UTF-8");
|
||||
QString content = stream.readAll();
|
||||
file.close();
|
||||
return content;
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString CommandProcessor::getDateDifferenceString(const QDate &inputDate)
|
||||
{
|
||||
if (!inputDate.isValid() || inputDate.year() < 2000) {
|
||||
return "неизвестно";
|
||||
}
|
||||
|
||||
QDate currentDate = QDate::currentDate();
|
||||
if (currentDate < inputDate) {
|
||||
return "в будущем";
|
||||
}
|
||||
|
||||
int years = inputDate.daysTo(currentDate) / 365;
|
||||
QDate tempDate = inputDate.addYears(years);
|
||||
|
||||
int months = 0;
|
||||
while (tempDate.addMonths(1) <= currentDate) {
|
||||
months++;
|
||||
tempDate = tempDate.addMonths(1);
|
||||
}
|
||||
|
||||
int days = tempDate.daysTo(currentDate);
|
||||
if (days >= 30) {
|
||||
months += days / 30;
|
||||
days = days % 30;
|
||||
}
|
||||
|
||||
QString result;
|
||||
if (years > 0) {
|
||||
result += QString("%1 %2 ").arg(years).arg(getPeriodEnding(years, 0));
|
||||
}
|
||||
if (months > 0) {
|
||||
result += QString("%1 %2 ").arg(months).arg(getPeriodEnding(months, 1));
|
||||
}
|
||||
if (days > 0 || result.isEmpty()) {
|
||||
result += QString("%1 %2").arg(days).arg(getPeriodEnding(days, 2));
|
||||
}
|
||||
|
||||
return result.trimmed();
|
||||
}
|
||||
|
||||
QString CommandProcessor::getPeriodEnding(int n, int r)
|
||||
{
|
||||
static const QVector<QVector<QString>> endings = {
|
||||
{"год", "года", "лет"},
|
||||
{"месяц", "месяца", "месяцев"},
|
||||
{"день", "дня", "дней"}
|
||||
};
|
||||
|
||||
if (r < 0 || r >= endings.size()) return "";
|
||||
|
||||
if (n % 10 == 1 && n % 100 != 11) {
|
||||
return endings[r][0];
|
||||
} else if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20)) {
|
||||
return endings[r][1];
|
||||
} else {
|
||||
return endings[r][2];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
#ifndef COMMANDPROCESSOR_H
|
||||
#define COMMANDPROCESSOR_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QMap>
|
||||
#include <QDate>
|
||||
#include "mediafilemanager.h"
|
||||
#include "user_manager.h"
|
||||
#include "ttw_api.h"
|
||||
#include "soundmanager.h"
|
||||
#include "neuralnetworkmanager.h"
|
||||
#include "randommanager.h"
|
||||
#include "randomresponses.h"
|
||||
|
||||
class CommandProcessor : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct Command {
|
||||
QString command;
|
||||
QString response;
|
||||
};
|
||||
|
||||
struct Context {
|
||||
UserManager* userManager = nullptr;
|
||||
TTwAPI* twitchAPI = nullptr;
|
||||
SoundManager* soundManager = nullptr;
|
||||
NeuralNetworkManager* neuralManager = nullptr;
|
||||
RandomManager* randomManager = nullptr;
|
||||
RandomResponses* randomResponses = nullptr;
|
||||
MediaFileManager* mediaFileManager = nullptr;
|
||||
QString channel;
|
||||
int notifyVolume = 50;
|
||||
};
|
||||
|
||||
explicit CommandProcessor(QObject *parent = nullptr);
|
||||
|
||||
void setContext(const Context& context);
|
||||
|
||||
QString generateResponse(QString userIndex, const QString &command, const QString &message = "");
|
||||
|
||||
void addCommand(const QString &command, const QString &response);
|
||||
void addCommands(const QVector<Command> &commands);
|
||||
void clearCommands();
|
||||
|
||||
Command findCommand(const QString &commandName) const;
|
||||
|
||||
private:
|
||||
Context m_context;
|
||||
QVector<Command> m_commands;
|
||||
|
||||
QString processCommand(const QString &sender, const QString &fullCommand, const QString &rawResponse);
|
||||
QString parseStatic(const QString &response, const QString &sender, const QString ¶meters);
|
||||
QString parseRandomNumbers(const QString &response);
|
||||
QString parseSounds(const QString &response);
|
||||
QString parseTextFiles(const QString &response);
|
||||
QString parseRandomGroups(const QString &response);
|
||||
QString parseBan(const QString &response, const QString &sender);
|
||||
QString parseAPI(const QString &response, const QString &sender);
|
||||
QString parseAI(const QString &response, const QString &question);
|
||||
|
||||
QString extractParameters(const QString &fullCommand);
|
||||
QString getUsernameByIndex(QString userIndex) const;
|
||||
QString getDateDifferenceString(const QDate &inputDate);
|
||||
QString getPeriodEnding(int n, int r);
|
||||
|
||||
QString getRandomResponseFromGroup(const QString &groupName);
|
||||
QString getTextFileContent(const QString &fileName);
|
||||
};
|
||||
|
||||
#endif // COMMANDPROCESSOR_H
|
||||
+1
-1
@@ -19,7 +19,7 @@ void EmoteProvider::setLogCallback(LogCallback callback) {
|
||||
m_logCallback = callback;
|
||||
}
|
||||
|
||||
void EmoteProvider::log(const QString &method, const QString &message, LogLevel level) {
|
||||
void EmoteProvider::log(const QString &method, const QString &message, LogLevelemt level) {
|
||||
if (m_logCallback) {
|
||||
m_logCallback(metaObject()->className(), method, message, level);
|
||||
}
|
||||
|
||||
+6
-4
@@ -9,16 +9,18 @@
|
||||
#include <QNetworkReply>
|
||||
#include <functional>
|
||||
|
||||
enum LogLevel {
|
||||
enum LogLevelemt {
|
||||
LOG_INFO = 0,
|
||||
LOG_WARNING = 1,
|
||||
LOG_ERROR = 2
|
||||
LOG_ERROR = 2,
|
||||
LOG_DEBUG = 3
|
||||
|
||||
};
|
||||
|
||||
using LogCallback = std::function<void(const QString& module,
|
||||
const QString& method,
|
||||
const QString& message,
|
||||
LogLevel level)>;
|
||||
LogLevelemt level)>;
|
||||
|
||||
struct BTTVEmote {
|
||||
QString id;
|
||||
@@ -50,7 +52,7 @@ signals:
|
||||
void emotesLoaded();
|
||||
|
||||
protected:
|
||||
void log(const QString &method, const QString &message, LogLevel level);
|
||||
void log(const QString &method, const QString &message, LogLevelemt level);
|
||||
virtual QString getBaseUrl() const = 0;
|
||||
virtual void parseGlobalResponse(const QByteArray &data) = 0;
|
||||
virtual void parseCustomResponse(const QByteArray &data, const QString &userId) = 0;
|
||||
|
||||
+170
-68
@@ -1,4 +1,5 @@
|
||||
#include "fcreatenotify.h"
|
||||
#include "udatabase.h"
|
||||
#include "ui_fcreatenotify.h"
|
||||
#include <QFileDialog>
|
||||
#include <QDebug>
|
||||
@@ -7,15 +8,19 @@
|
||||
#include <QCloseEvent>
|
||||
#include <QDesktopServices>
|
||||
#include <QApplication>
|
||||
#include "filemanager.h"
|
||||
|
||||
FCreateNotify::FCreateNotify(QWidget *parent) :
|
||||
FCreateNotify::FCreateNotify(uDataBase *database, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::FCreateNotify),
|
||||
m_server(nullptr),
|
||||
m_notificationCounter(0)
|
||||
m_isEditMode(false),
|
||||
m_database(database),
|
||||
m_existingServerName(""),
|
||||
m_server(nullptr), // Добавьте
|
||||
m_notificationCounter(0) // Добавьте
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
FileManager::instance().initializeFolderStructure();
|
||||
// Создаем структуру папок для статических файлов
|
||||
QDir appDir(QApplication::applicationDirPath());
|
||||
appDir.mkpath("sounds");
|
||||
@@ -27,8 +32,7 @@ FCreateNotify::FCreateNotify(QWidget *parent) :
|
||||
"donation", "follow", "subscription", "raid",
|
||||
"bits", "host", "merch", "goal", "poll", "prediction"
|
||||
};
|
||||
ui->cbEvent->addItems(events);
|
||||
ui->cbEvent->setCurrentIndex(0);
|
||||
|
||||
// Устанавливаем значения по умолчанию
|
||||
ui->edtHeader->setText("Тестовый пользователь");
|
||||
ui->edtMessage->setText("Это тестовое уведомление!");
|
||||
@@ -40,7 +44,7 @@ FCreateNotify::FCreateNotify(QWidget *parent) :
|
||||
// Информация для пользователя
|
||||
ui->btnTest->setToolTip("Создать тестовое уведомление и открыть браузер");
|
||||
ui->btnAdd->setToolTip("Добавить уведомление для отображения");
|
||||
m_server = new HttpServer(nullptr);
|
||||
|
||||
}
|
||||
|
||||
FCreateNotify::~FCreateNotify()
|
||||
@@ -101,25 +105,7 @@ void FCreateNotify::on_btnTest_clicked()
|
||||
createNotification(true);
|
||||
}
|
||||
|
||||
void FCreateNotify::on_btnAdd_clicked()
|
||||
{
|
||||
// Создаем сервер с текущими настройками
|
||||
createServer();
|
||||
|
||||
if (!m_server) {
|
||||
QMessageBox::warning(this, "Ошибка", "Не удалось создать сервер");
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем название сервера
|
||||
QString name = QString("Уведомления (порт %1)").arg(m_server->port());
|
||||
|
||||
// Отправляем сигнал с созданным сервером
|
||||
emit serverCreated(m_server, name);
|
||||
|
||||
// Закрываем окно
|
||||
accept();
|
||||
}
|
||||
|
||||
void FCreateNotify::createNotification(bool isTest)
|
||||
{
|
||||
@@ -142,39 +128,24 @@ void FCreateNotify::createNotification(bool isTest)
|
||||
QString("Тестовое сообщение #%1").arg(m_notificationCounter) :
|
||||
ui->edtMessage->text();
|
||||
|
||||
// Добавляем тип события
|
||||
notif.content = QString("[%1] %2")
|
||||
.arg(ui->cbEvent->currentText())
|
||||
.arg(notif.content);
|
||||
|
||||
// Картинка
|
||||
QString imgPath = ui->edtFileImg->text();
|
||||
if (!imgPath.isEmpty() && QFile::exists(imgPath)) {
|
||||
QFileInfo imgInfo(imgPath);
|
||||
QString destFileName = imgInfo.fileName();
|
||||
QString destPath = getAbsolutePath("imgs/" + destFileName);
|
||||
|
||||
// Копируем, если файл еще не существует
|
||||
if (!QFile::exists(destPath)) {
|
||||
QFile::copy(imgPath, destPath);
|
||||
QString newFileName;
|
||||
if (FileManager::instance().copyToUserData(imgPath, FileManager::WebServerImages,
|
||||
FileManager::CopyWithNewName, &newFileName)) {
|
||||
notif.url = FileManager::instance().getWebPath(FileManager::WebServerImages, newFileName);
|
||||
}
|
||||
|
||||
notif.url = "/imgs/" + destFileName;
|
||||
}
|
||||
|
||||
// Звук
|
||||
QString soundPath = ui->edtFileSong->text();
|
||||
if (!soundPath.isEmpty() && QFile::exists(soundPath)) {
|
||||
QFileInfo soundInfo(soundPath);
|
||||
QString destFileName = soundInfo.fileName();
|
||||
QString destPath = getAbsolutePath("sounds/" + destFileName);
|
||||
|
||||
// Копируем, если файл еще не существует
|
||||
if (!QFile::exists(destPath)) {
|
||||
QFile::copy(soundPath, destPath);
|
||||
QString newFileName;
|
||||
if (FileManager::instance().copyToUserData(soundPath, FileManager::WebServerSounds,
|
||||
FileManager::CopyWithNewName, &newFileName)) {
|
||||
notif.soundURL = FileManager::instance().getWebPath(FileManager::WebServerSounds, newFileName);
|
||||
}
|
||||
|
||||
notif.soundURL = "/sounds/" + destFileName;
|
||||
}
|
||||
|
||||
// ПОЛУЧАЕМ НАСТРОЙКИ ИЗ ВИДЖЕТОВ
|
||||
@@ -277,26 +248,6 @@ void FCreateNotify::createNotification(bool isTest)
|
||||
}
|
||||
}
|
||||
|
||||
void FCreateNotify::copyFileToAppDir(const QString &sourcePath, const QString &destSubDir)
|
||||
{
|
||||
QFileInfo sourceInfo(sourcePath);
|
||||
QString destPath = getAbsolutePath(destSubDir + "/" + sourceInfo.fileName());
|
||||
|
||||
// Копируем файл, если он еще не существует или изменился
|
||||
if (!QFile::exists(destPath) ||
|
||||
QFileInfo(sourcePath).lastModified() > QFileInfo(destPath).lastModified()) {
|
||||
QFile::remove(destPath); // Удаляем старую версию
|
||||
if (QFile::copy(sourcePath, destPath)) {
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString FCreateNotify::getAbsolutePath(const QString &relativePath)
|
||||
{
|
||||
return QApplication::applicationDirPath() + "/" + relativePath;
|
||||
}
|
||||
|
||||
void FCreateNotify::on_btnOpenImg_clicked()
|
||||
{
|
||||
QString fileName = QFileDialog::getOpenFileName(this,
|
||||
@@ -336,3 +287,154 @@ void FCreateNotify::onServerStarted(bool success)
|
||||
QMessageBox::warning(this, "Ошибка", "Не удалось запустить веб-сервер");
|
||||
}
|
||||
}
|
||||
|
||||
void FCreateNotify::setEditMode(bool isEditMode)
|
||||
{
|
||||
m_isEditMode = isEditMode;
|
||||
if (isEditMode) {
|
||||
ui->btnAdd->setText("Изменить");
|
||||
setWindowTitle("TTW Bot app: Редактировать уведомления");
|
||||
} else {
|
||||
ui->btnAdd->setText("Создать");
|
||||
setWindowTitle("TTW Bot app: Создать уведомления");
|
||||
}
|
||||
}
|
||||
|
||||
void FCreateNotify::loadExistingServer(HttpServer *server, const QString &name)
|
||||
{
|
||||
if (!server) return;
|
||||
|
||||
m_isEditMode = true;
|
||||
m_server = server;
|
||||
m_existingServerName = name;
|
||||
|
||||
setEditMode(true);
|
||||
|
||||
// Устанавливаем порт
|
||||
FSettingsWS *settingsWS = ui->widget;
|
||||
if (settingsWS) {
|
||||
settingsWS->sbPort->setValue(server->port());
|
||||
settingsWS->sbTime->setValue(server->getDuration());
|
||||
}
|
||||
|
||||
// Устанавливаем название
|
||||
ui->lineEdit->setText(name); // Предполагается, что есть lineEdit для имени
|
||||
|
||||
// Устанавливаем цвета
|
||||
FColorSetting *colorSetting = ui->wBlock;
|
||||
if (colorSetting) {
|
||||
colorSetting->cbBlockColor->setCurrentText(server->getBlockColor());
|
||||
colorSetting->cbBorderColor->setCurrentText(server->getBorderColor());
|
||||
colorSetting->sbBorderSize->setValue(server->getBorderSize());
|
||||
colorSetting->cbBackgroundColor->setCurrentText(server->getPageBackgroundColor());
|
||||
colorSetting->hsBlockTransparant->setValue(server->getTransparency());
|
||||
}
|
||||
|
||||
// Устанавливаем шрифты
|
||||
FFontSetting *fontHeaderSetting = ui->wFont;
|
||||
if (fontHeaderSetting) {
|
||||
QString titleFamily;
|
||||
int titleSize;
|
||||
QString titleColor;
|
||||
server->getTitleFont(titleFamily, titleSize, titleColor);
|
||||
|
||||
fontHeaderSetting->cbFontStyle->setCurrentText(titleFamily);
|
||||
fontHeaderSetting->sbFontSize->setValue(titleSize);
|
||||
fontHeaderSetting->cbFontColor->setCurrentText(titleColor);
|
||||
}
|
||||
|
||||
FFontSetting *fontMessageSetting = ui->wFont_2;
|
||||
if (fontMessageSetting) {
|
||||
QString contentFamily;
|
||||
int contentSize;
|
||||
QString contentColor;
|
||||
server->getContentFont(contentFamily, contentSize, contentColor);
|
||||
|
||||
fontMessageSetting->cbFontStyle->setCurrentText(contentFamily);
|
||||
fontMessageSetting->sbFontSize->setValue(contentSize);
|
||||
fontMessageSetting->cbFontColor->setCurrentText(contentColor);
|
||||
}
|
||||
}
|
||||
|
||||
// Обновите on_btnAdd_clicked для поддержки редактирования:
|
||||
void FCreateNotify::on_btnAdd_clicked()
|
||||
{
|
||||
if (m_isEditMode && m_server) {
|
||||
// Режим редактирования
|
||||
FSettingsWS *settingsWS = ui->widget;
|
||||
FColorSetting *colorSetting = ui->wBlock;
|
||||
FFontSetting *fontHeaderSetting = ui->wFont;
|
||||
FFontSetting *fontMessageSetting = ui->wFont_2;
|
||||
|
||||
if (!settingsWS || !colorSetting || !fontHeaderSetting || !fontMessageSetting) {
|
||||
QMessageBox::warning(this, "Ошибка", "Не найдены настройки сервера");
|
||||
return;
|
||||
}
|
||||
|
||||
int newPort = settingsWS->sbPort->value();
|
||||
bool portChanged = (newPort != m_server->port());
|
||||
|
||||
if (portChanged) {
|
||||
m_server->stop();
|
||||
delete m_server;
|
||||
m_server = nullptr;
|
||||
createServer();
|
||||
}
|
||||
|
||||
if (!m_server) {
|
||||
QMessageBox::warning(this, "Ошибка", "Не удалось обновить сервер");
|
||||
return;
|
||||
}
|
||||
|
||||
// Применяем настройки к серверу
|
||||
// Цвета
|
||||
m_server->setBlockColor(colorSetting->cbBlockColor->currentText());
|
||||
m_server->setBorderColor(colorSetting->cbBorderColor->currentText());
|
||||
m_server->setBorderSize(colorSetting->sbBorderSize->value());
|
||||
m_server->setPageBackgroundColor(colorSetting->cbBackgroundColor->currentText());
|
||||
m_server->setTransparency(colorSetting->hsBlockTransparant->value());
|
||||
|
||||
// Шрифты
|
||||
m_server->setTitleFont(
|
||||
fontHeaderSetting->cbFontStyle->currentText(),
|
||||
fontHeaderSetting->sbFontSize->value(),
|
||||
fontHeaderSetting->cbFontColor->currentText()
|
||||
);
|
||||
|
||||
m_server->setContentFont(
|
||||
fontMessageSetting->cbFontStyle->currentText(),
|
||||
fontMessageSetting->sbFontSize->value(),
|
||||
fontMessageSetting->cbFontColor->currentText()
|
||||
);
|
||||
|
||||
// Длительность
|
||||
m_server->setDuration(settingsWS->sbTime->value());
|
||||
|
||||
QString newName = ui->lineEdit->text();
|
||||
if (newName.isEmpty()) {
|
||||
newName = QString("Уведомления (порт %1)").arg(m_server->port());
|
||||
}
|
||||
|
||||
emit serverUpdated(m_server, newName);
|
||||
accept();
|
||||
} else {
|
||||
// Режим создания (существующий код)
|
||||
createServer();
|
||||
|
||||
if (!m_server) {
|
||||
QMessageBox::warning(this, "Ошибка", "Не удалось создать сервер");
|
||||
return;
|
||||
}
|
||||
|
||||
createNotification(false);
|
||||
|
||||
QString name = ui->lineEdit->text();
|
||||
if (name.isEmpty()) {
|
||||
name = QString("Уведомления (порт %1)").arg(m_server->port());
|
||||
}
|
||||
|
||||
emit serverCreated(m_server, name);
|
||||
m_server = nullptr;
|
||||
accept();
|
||||
}
|
||||
}
|
||||
|
||||
+9
-4
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "webservernotify.h"
|
||||
#include <QDialog>
|
||||
#include "udatabase.h"
|
||||
|
||||
namespace Ui {
|
||||
class FCreateNotify;
|
||||
@@ -14,11 +15,12 @@ class FCreateNotify : public QDialog
|
||||
|
||||
signals:
|
||||
void serverCreated(HttpServer *server, const QString &name);
|
||||
|
||||
void serverUpdated(HttpServer *server, const QString &name);
|
||||
public:
|
||||
explicit FCreateNotify(QWidget *parent = nullptr);
|
||||
explicit FCreateNotify(uDataBase *database = nullptr, QWidget *parent = nullptr);
|
||||
~FCreateNotify();
|
||||
|
||||
void loadExistingServer(HttpServer *server, const QString &name);
|
||||
void setEditMode(bool isEditMode);
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
|
||||
@@ -34,11 +36,14 @@ private:
|
||||
void createNotification(bool isTest = false);
|
||||
QString getAbsolutePath(const QString &relativePath);
|
||||
void copyFileToAppDir(const QString &sourcePath, const QString &destSubDir);
|
||||
|
||||
bool m_isEditMode;
|
||||
uDataBase *m_database;
|
||||
QString m_existingServerName;
|
||||
// Сервер
|
||||
HttpServer *m_server;
|
||||
int m_notificationCounter;
|
||||
void createServer();
|
||||
void applyCurrentSettingsToServer();
|
||||
};
|
||||
|
||||
#endif // FCREATENOTIFY_H
|
||||
|
||||
+23
-23
@@ -137,7 +137,7 @@
|
||||
<x>370</x>
|
||||
<y>150</y>
|
||||
<width>351</width>
|
||||
<height>181</height>
|
||||
<height>141</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="title">
|
||||
@@ -149,7 +149,7 @@
|
||||
<x>9</x>
|
||||
<y>19</y>
|
||||
<width>331</width>
|
||||
<height>152</height>
|
||||
<height>111</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
@@ -163,8 +163,8 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QPushButton" name="btnOpenSong">
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="btnOpenImg">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
@@ -173,6 +173,13 @@
|
||||
<item row="1" column="0">
|
||||
<widget class="QLineEdit" name="edtFileImg"/>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QPushButton" name="btnOpenSong">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
@@ -180,23 +187,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="btnOpenImg">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Событие</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QComboBox" name="cbEvent"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
@@ -204,7 +194,7 @@
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>370</x>
|
||||
<y>340</y>
|
||||
<y>300</y>
|
||||
<width>80</width>
|
||||
<height>24</height>
|
||||
</rect>
|
||||
@@ -217,7 +207,7 @@
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>460</x>
|
||||
<y>340</y>
|
||||
<y>300</y>
|
||||
<width>81</width>
|
||||
<height>24</height>
|
||||
</rect>
|
||||
@@ -226,6 +216,16 @@
|
||||
<string>Создать</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="lineEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>370</x>
|
||||
<y>400</y>
|
||||
<width>231</width>
|
||||
<height>22</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
||||
+27
-39
@@ -1,4 +1,5 @@
|
||||
#include "ffontsetting.h"
|
||||
#include "filemanager.h"
|
||||
#include "qdebug.h"
|
||||
#include "qdir.h"
|
||||
#include "ui_ffontsetting.h"
|
||||
@@ -48,58 +49,45 @@ void FFontSetting::setupColorComboBox(QComboBox* comboBox)
|
||||
|
||||
void FFontSetting::loadFonts()
|
||||
{
|
||||
QString fontsPath = "fonts";
|
||||
QDir fontsDir(fontsPath);
|
||||
// Загружаем шрифты из пользовательской папки
|
||||
QString userFontsPath = FileManager::instance().getPath(FileManager::Fonts);
|
||||
QDir userFontsDir(userFontsPath);
|
||||
|
||||
if (fontsDir.exists()) {
|
||||
QStringList fontFiles = fontsDir.entryList(QStringList() << "*.ttf" << "*.otf" << "*.ttc",
|
||||
QDir::Files);
|
||||
|
||||
// Список всех загруженных шрифтов
|
||||
QStringList allFontFamilies;
|
||||
|
||||
// Загружаем пользовательские шрифты
|
||||
if (userFontsDir.exists()) {
|
||||
QStringList fontFiles = userFontsDir.entryList(QStringList() << "*.ttf" << "*.otf" << "*.ttc", QDir::Files);
|
||||
|
||||
foreach (const QString &fontFile, fontFiles) {
|
||||
QString fontPath = fontsDir.absoluteFilePath(fontFile);
|
||||
QString fontPath = userFontsDir.absoluteFilePath(fontFile);
|
||||
int fontId = QFontDatabase::addApplicationFont(fontPath);
|
||||
|
||||
if (fontId != -1) {
|
||||
QStringList fontFamilies = QFontDatabase::applicationFontFamilies(fontId);
|
||||
} else {
|
||||
allFontFamilies.append(fontFamilies);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ОБНОВЛЯЕМ СПИСОК ШРИФТОВ В КОМБОБОКСЕ
|
||||
ui->cbFontStyle->clear(); // Очищаем старый список
|
||||
// Добавляем системные шрифты Windows
|
||||
QFontDatabase fontDatabase;
|
||||
QStringList systemFamilies = fontDatabase.families();
|
||||
allFontFamilies.append(systemFamilies);
|
||||
|
||||
// Получаем все шрифты через объект QFontDatabase
|
||||
QFontDatabase fontDatabase;
|
||||
QStringList fontFamilies = fontDatabase.families(); // Получаем все шрифты
|
||||
// Удаляем дубликаты
|
||||
allFontFamilies.removeDuplicates();
|
||||
allFontFamilies.sort();
|
||||
|
||||
// ИЛИ используйте статический метод с параметром:
|
||||
// QStringList fontFamilies = QFontDatabase::families(QFontDatabase::Any);
|
||||
// Заполняем ComboBox
|
||||
ui->cbFontStyle->clear();
|
||||
ui->cbFontStyle->addItems(allFontFamilies);
|
||||
|
||||
ui->cbFontStyle->addItems(fontFamilies); // Добавляем все шрифты
|
||||
|
||||
// Устанавливаем шрифт по умолчанию
|
||||
int defaultIndex = ui->cbFontStyle->findText("Arial");
|
||||
if (defaultIndex >= 0) {
|
||||
ui->cbFontStyle->setCurrentIndex(defaultIndex);
|
||||
}
|
||||
|
||||
// Показываем количество доступных шрифтов
|
||||
} else {
|
||||
|
||||
// Загружаем системные шрифты, если папки нет
|
||||
QFontDatabase fontDatabase;
|
||||
QStringList fontFamilies = fontDatabase.families();
|
||||
ui->cbFontStyle->clear();
|
||||
ui->cbFontStyle->addItems(fontFamilies);
|
||||
|
||||
int defaultIndex = ui->cbFontStyle->findText("Arial");
|
||||
if (defaultIndex >= 0) {
|
||||
ui->cbFontStyle->setCurrentIndex(defaultIndex);
|
||||
}
|
||||
|
||||
// Проверяем альтернативные пути
|
||||
QString appDir = QApplication::applicationDirPath();
|
||||
QString altPath = appDir + "/fonts";
|
||||
// Устанавливаем шрифт по умолчанию
|
||||
int defaultIndex = ui->cbFontStyle->findText("Arial");
|
||||
if (defaultIndex >= 0) {
|
||||
ui->cbFontStyle->setCurrentIndex(defaultIndex);
|
||||
}
|
||||
}
|
||||
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
#include "filemanager.h"
|
||||
#include <QDebug>
|
||||
|
||||
FileManager::FileManager()
|
||||
{
|
||||
m_systemPath = QCoreApplication::applicationDirPath();
|
||||
m_userDataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
}
|
||||
|
||||
FileManager& FileManager::instance()
|
||||
{
|
||||
static FileManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
QString FileManager::systemPath() const
|
||||
{
|
||||
return m_systemPath;
|
||||
}
|
||||
|
||||
QString FileManager::userDataPath() const
|
||||
{
|
||||
return m_userDataPath;
|
||||
}
|
||||
|
||||
QString FileManager::getPath(FileType type, const QString& subPath) const
|
||||
{
|
||||
QString basePath;
|
||||
|
||||
// Определяем базовый путь (системный или пользовательский)
|
||||
switch (type) {
|
||||
case Icons:
|
||||
case SystemStyles:
|
||||
basePath = m_systemPath;
|
||||
break;
|
||||
default:
|
||||
basePath = m_userDataPath;
|
||||
break;
|
||||
}
|
||||
|
||||
// Добавляем подпапку
|
||||
QString folder;
|
||||
switch (type) {
|
||||
case Sounds: folder = "sounds"; break;
|
||||
case Images: folder = "images"; break;
|
||||
case Styles: folder = "styles"; break;
|
||||
case Voices: folder = "voices"; break;
|
||||
case Fonts: folder = "fonts"; break;
|
||||
case Temp: folder = "temp"; break;
|
||||
case Backups: folder = "backups"; break;
|
||||
case Exports: folder = "exports"; break;
|
||||
case Logs: folder = "logs"; break;
|
||||
case Cache: folder = "cache"; break;
|
||||
case WebServerImages: folder = "webserver/images"; break;
|
||||
case WebServerSounds: folder = "webserver/sounds"; break;
|
||||
case Icons: folder = "ico"; break;
|
||||
case SystemStyles: folder = "styles"; break;
|
||||
default: folder = "";
|
||||
}
|
||||
|
||||
QString path = QString("%1/%2").arg(basePath).arg(folder);
|
||||
if (!subPath.isEmpty()) {
|
||||
path += "/" + subPath;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
QString FileManager::getFullPath(FileType type, const QString& fileName) const
|
||||
{
|
||||
QString path = getPath(type);
|
||||
return QString("%1/%2").arg(path).arg(fileName);
|
||||
}
|
||||
|
||||
bool FileManager::copyToUserData(const QString& sourceFile, FileType type,
|
||||
CopyMode mode, QString* newFileName)
|
||||
{
|
||||
QFileInfo sourceInfo(sourceFile);
|
||||
if (!sourceInfo.exists()) {
|
||||
qWarning() << "FileManager: Source file doesn't exist:" << sourceFile;
|
||||
return false;
|
||||
}
|
||||
|
||||
QString destFolder = getPath(type);
|
||||
QDir dir(destFolder);
|
||||
if (!dir.exists()) {
|
||||
if (!dir.mkpath(".")) {
|
||||
qWarning() << "FileManager: Cannot create folder:" << destFolder;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QString destFileName = sourceInfo.fileName();
|
||||
QString destPath = getFullPath(type, destFileName);
|
||||
|
||||
// Обрабатываем конфликты имен
|
||||
if (QFile::exists(destPath)) {
|
||||
switch (mode) {
|
||||
case CopyIfNotExists:
|
||||
// Файл уже существует, не копируем
|
||||
if (newFileName) *newFileName = destFileName;
|
||||
return true;
|
||||
|
||||
case CopyAlways:
|
||||
// Перезаписываем существующий файл
|
||||
QFile::remove(destPath);
|
||||
break;
|
||||
|
||||
case CopyWithNewName:
|
||||
// Генерируем уникальное имя
|
||||
int counter = 1;
|
||||
QString baseName = sourceInfo.baseName();
|
||||
QString suffix = sourceInfo.suffix();
|
||||
|
||||
while (QFile::exists(destPath)) {
|
||||
destFileName = QString("%1_%2.%3").arg(baseName).arg(counter).arg(suffix);
|
||||
destPath = getFullPath(type, destFileName);
|
||||
counter++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Копируем файл
|
||||
if (QFile::copy(sourceFile, destPath)) {
|
||||
if (newFileName) *newFileName = destFileName;
|
||||
return true;
|
||||
}
|
||||
|
||||
qWarning() << "FileManager: Failed to copy file from" << sourceFile << "to" << destPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FileManager::existsInUserData(FileType type, const QString& fileName) const
|
||||
{
|
||||
QString path = getFullPath(type, fileName);
|
||||
return QFile::exists(path);
|
||||
}
|
||||
|
||||
void FileManager::initializeFolderStructure()
|
||||
{
|
||||
// Создаем все пользовательские папки
|
||||
for (int i = Sounds; i <= WebServerSounds; ++i) {
|
||||
FileType type = static_cast<FileType>(i);
|
||||
QString path = getPath(type);
|
||||
QDir dir(path);
|
||||
if (!dir.exists()) {
|
||||
dir.mkpath(".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString FileManager::getWebPath(FileType type, const QString& fileName) const
|
||||
{
|
||||
QString folder;
|
||||
switch (type) {
|
||||
case WebServerImages: folder = "imgs"; break;
|
||||
case WebServerSounds: folder = "sounds"; break;
|
||||
default: return QString();
|
||||
}
|
||||
|
||||
return QString("/%1/%2").arg(folder).arg(fileName);
|
||||
}
|
||||
|
||||
void FileManager::copyDefaultFiles()
|
||||
{
|
||||
// Копируем системные стили в пользовательскую папку при первом запуске
|
||||
QString systemStylesPath = getPath(SystemStyles);
|
||||
QDir systemStylesDir(systemStylesPath);
|
||||
|
||||
if (systemStylesDir.exists()) {
|
||||
QStringList qssFiles = systemStylesDir.entryList(QStringList() << "*.qss" << "*.QSS", QDir::Files);
|
||||
|
||||
for (const QString& file : qssFiles) {
|
||||
QString sourcePath = systemStylesDir.absoluteFilePath(file);
|
||||
QString destPath = getFullPath(Styles, file);
|
||||
|
||||
if (!QFile::exists(destPath)) {
|
||||
QFile::copy(sourcePath, destPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
#ifndef FILEMANAGER_H
|
||||
#define FILEMANAGER_H
|
||||
|
||||
#include <QString>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QStandardPaths>
|
||||
#include <QCoreApplication>
|
||||
|
||||
class FileManager
|
||||
{
|
||||
public:
|
||||
// Типы файлов/папок
|
||||
enum FileType {
|
||||
Sounds,
|
||||
Images,
|
||||
Styles,
|
||||
Voices,
|
||||
Fonts,
|
||||
Temp,
|
||||
Backups,
|
||||
Exports,
|
||||
Logs,
|
||||
Cache,
|
||||
WebServerImages,
|
||||
WebServerSounds,
|
||||
Icons,
|
||||
SystemStyles
|
||||
};
|
||||
|
||||
// Режим копирования
|
||||
enum CopyMode {
|
||||
CopyIfNotExists, // Копировать только если не существует
|
||||
CopyAlways, // Всегда копировать (перезаписывать)
|
||||
CopyWithNewName // Копировать с новым именем (если существует)
|
||||
};
|
||||
|
||||
static FileManager& instance();
|
||||
|
||||
// Основные пути
|
||||
QString systemPath() const;
|
||||
QString userDataPath() const;
|
||||
|
||||
// Получение путей
|
||||
QString getPath(FileType type, const QString& subPath = "") const;
|
||||
QString getFullPath(FileType type, const QString& fileName) const;
|
||||
|
||||
// Копирование файлов
|
||||
bool copyToUserData(const QString& sourceFile, FileType type,
|
||||
CopyMode mode = CopyIfNotExists,
|
||||
QString* newFileName = nullptr);
|
||||
|
||||
// Проверка существования
|
||||
bool existsInUserData(FileType type, const QString& fileName) const;
|
||||
|
||||
// Инициализация структуры папок
|
||||
void initializeFolderStructure();
|
||||
|
||||
// Получение относительного пути для веб-сервера
|
||||
QString getWebPath(FileType type, const QString& fileName) const;
|
||||
|
||||
// Копирование файлов из системных в пользовательские (при первом запуске)
|
||||
void copyDefaultFiles();
|
||||
|
||||
private:
|
||||
FileManager();
|
||||
~FileManager() = default;
|
||||
FileManager(const FileManager&) = delete;
|
||||
FileManager& operator=(const FileManager&) = delete;
|
||||
|
||||
QString m_systemPath;
|
||||
QString m_userDataPath;
|
||||
};
|
||||
|
||||
#endif // FILEMANAGER_H
|
||||
+832
@@ -0,0 +1,832 @@
|
||||
#include "logmanager.h"
|
||||
#include <QCoreApplication>
|
||||
#include <QStandardPaths>
|
||||
#include <QDir>
|
||||
#include <QDateTime>
|
||||
#include <QThread>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QDebug>
|
||||
|
||||
// ============================================================================
|
||||
// СТАТИЧЕСКИЕ ПЕРЕМЕННЫЕ
|
||||
// ============================================================================
|
||||
|
||||
LogManager* LogManager::m_instance = nullptr;
|
||||
|
||||
// ============================================================================
|
||||
// МЕТОДЫ LOGENTRY
|
||||
// ============================================================================
|
||||
|
||||
QString LogEntry::toString() const
|
||||
{
|
||||
QStringList parts;
|
||||
|
||||
if (!timestamp.isNull()) {
|
||||
parts.append(timestamp.toString("hh:mm:ss"));
|
||||
}
|
||||
|
||||
parts.append(levelToString(level));
|
||||
|
||||
if (!module.isEmpty()) {
|
||||
parts.append(module);
|
||||
}
|
||||
|
||||
if (!method.isEmpty()) {
|
||||
parts.append(method);
|
||||
}
|
||||
|
||||
parts.append(message);
|
||||
|
||||
return parts.join(" | ");
|
||||
}
|
||||
|
||||
QString LogEntry::toHtml() const
|
||||
{
|
||||
QString color = levelToColor(level).name();
|
||||
|
||||
QString html = QString(
|
||||
"<div style=\"margin: 2px 0; padding: 2px; border-left: 3px solid %1;\">"
|
||||
"<span style=\"color: gray; font-size: 0.8em;\">%2</span> "
|
||||
"<span style=\"color: %1; font-weight: bold;\">[%3]</span> "
|
||||
"<span style=\"color: darkblue;\">%4</span> "
|
||||
"<span style=\"color: green;\">%5</span>: "
|
||||
"<span>%6</span>"
|
||||
"</div>")
|
||||
.arg(color)
|
||||
.arg(timestamp.toString("hh:mm:ss"))
|
||||
.arg(levelToString(level))
|
||||
.arg(module)
|
||||
.arg(method)
|
||||
.arg(message.toHtmlEscaped());
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
QString LogEntry::toCsv() const
|
||||
{
|
||||
return QString("\"%1\",\"%2\",\"%3\",\"%4\",\"%5\"")
|
||||
.arg(timestamp.toString("yyyy-MM-dd hh:mm:ss"))
|
||||
.arg(levelToString(level))
|
||||
.arg(module)
|
||||
.arg(method)
|
||||
.arg(message);
|
||||
}
|
||||
|
||||
QString LogEntry::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj["timestamp"] = timestamp.toString(Qt::ISODate);
|
||||
obj["level"] = levelToString(level);
|
||||
obj["module"] = module;
|
||||
obj["method"] = method;
|
||||
obj["message"] = message;
|
||||
obj["threadId"] = static_cast<qint64>(threadId);
|
||||
|
||||
if (!sourceFile.isEmpty()) {
|
||||
obj["sourceFile"] = sourceFile;
|
||||
obj["sourceLine"] = sourceLine;
|
||||
}
|
||||
|
||||
QJsonDocument doc(obj);
|
||||
return doc.toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
QString LogEntry::levelToString(LogLevel level)
|
||||
{
|
||||
switch (level) {
|
||||
case LogLevel::Info: return "INFO";
|
||||
case LogLevel::Warning: return "WARNING";
|
||||
case LogLevel::Error: return "ERROR";
|
||||
case LogLevel::Debug: return "DEBUG";
|
||||
case LogLevel::Critical: return "CRITICAL";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
QColor LogEntry::levelToColor(LogLevel level)
|
||||
{
|
||||
switch (level) {
|
||||
case LogLevel::Info: return Qt::darkGreen;
|
||||
case LogLevel::Warning: return Qt::darkYellow;
|
||||
case LogLevel::Error: return Qt::red;
|
||||
case LogLevel::Debug: return Qt::gray;
|
||||
case LogLevel::Critical: return Qt::darkRed;
|
||||
default: return Qt::black;
|
||||
}
|
||||
}
|
||||
|
||||
QString LogEntry::formatDateTime(const QDateTime& dt)
|
||||
{
|
||||
return dt.toString("dd.MM.yyyy hh:mm:ss.zzz");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// РЕАЛИЗАЦИЯ LOGMANAGER
|
||||
// ============================================================================
|
||||
|
||||
LogManager::LogManager(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_maxEntries(10000)
|
||||
{
|
||||
// Настройки по умолчанию
|
||||
m_settings.logToFile = true;
|
||||
m_settings.logToConsole = true;
|
||||
m_settings.logToDatabase = false;
|
||||
|
||||
// Стандартный путь к файлу логов
|
||||
QString appDataPath = QStandardPaths::writableLocation(
|
||||
QStandardPaths::AppDataLocation);
|
||||
QDir dir(appDataPath);
|
||||
if (!dir.exists()) {
|
||||
dir.mkpath(".");
|
||||
}
|
||||
m_settings.logFilePath = dir.filePath("application.log");
|
||||
|
||||
// Цвета по умолчанию
|
||||
m_settings.colors[LogLevel::Info] = Qt::darkGreen;
|
||||
m_settings.colors[LogLevel::Warning] = Qt::darkYellow;
|
||||
m_settings.colors[LogLevel::Error] = Qt::red;
|
||||
m_settings.colors[LogLevel::Debug] = Qt::gray;
|
||||
m_settings.colors[LogLevel::Critical] = Qt::darkRed;
|
||||
|
||||
m_settings.showTimestamp = true;
|
||||
m_settings.showModule = true;
|
||||
m_settings.showMethod = true;
|
||||
}
|
||||
|
||||
LogManager::~LogManager()
|
||||
{
|
||||
if (m_logFile.isOpen()) {
|
||||
m_logFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
LogManager* LogManager::instance()
|
||||
{
|
||||
if (!m_instance) {
|
||||
m_instance = new LogManager();
|
||||
}
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
void LogManager::initialize(const LogSettings& settings)
|
||||
{
|
||||
if (m_instance) {
|
||||
m_instance->updateSettings(settings);
|
||||
} else {
|
||||
m_instance = new LogManager();
|
||||
m_instance->updateSettings(settings);
|
||||
}
|
||||
}
|
||||
|
||||
void LogManager::cleanup()
|
||||
{
|
||||
if (m_instance) {
|
||||
delete m_instance;
|
||||
m_instance = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void LogManager::log(LogLevel level, const QString& module,
|
||||
const QString& method, const QString& message)
|
||||
{
|
||||
log(level, module, method, message, "", 0);
|
||||
}
|
||||
|
||||
void LogManager::log(LogLevel level, const QString& module,
|
||||
const QString& method, const QString& message,
|
||||
const QString& sourceFile, int sourceLine)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
|
||||
// Создаем запись
|
||||
LogEntry entry;
|
||||
entry.timestamp = QDateTime::currentDateTime();
|
||||
entry.level = level;
|
||||
entry.module = module;
|
||||
entry.method = method;
|
||||
entry.message = message;
|
||||
entry.threadId = reinterpret_cast<qint64>(QThread::currentThreadId());
|
||||
|
||||
if (!sourceFile.isEmpty()) {
|
||||
entry.sourceFile = sourceFile;
|
||||
entry.sourceLine = sourceLine;
|
||||
}
|
||||
|
||||
// Добавляем в список
|
||||
m_entries.append(entry);
|
||||
|
||||
// Ограничиваем количество записей
|
||||
if (m_entries.size() > m_maxEntries) {
|
||||
m_entries.removeFirst();
|
||||
}
|
||||
|
||||
// Записываем в файл
|
||||
if (m_settings.logToFile && !m_settings.logFilePath.isEmpty()) {
|
||||
writeToFile(entry);
|
||||
}
|
||||
|
||||
// Выводим в консоль
|
||||
if (m_settings.logToConsole) {
|
||||
QString formatted = formatForConsole(entry);
|
||||
switch (level) {
|
||||
case LogLevel::Error:
|
||||
case LogLevel::Critical:
|
||||
qCritical().noquote() << formatted;
|
||||
break;
|
||||
case LogLevel::Warning:
|
||||
qWarning().noquote() << formatted;
|
||||
break;
|
||||
case LogLevel::Debug:
|
||||
qDebug().noquote() << formatted;
|
||||
break;
|
||||
default:
|
||||
qInfo().noquote() << formatted;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
locker.unlock();
|
||||
|
||||
// Отправляем сигналы
|
||||
emit entryAdded(entry);
|
||||
|
||||
// Сигналы для конкретных уровней
|
||||
switch (level) {
|
||||
case LogLevel::Info:
|
||||
emit infoAdded(entry);
|
||||
break;
|
||||
case LogLevel::Warning:
|
||||
emit warningAdded(entry);
|
||||
break;
|
||||
case LogLevel::Error:
|
||||
emit errorAdded(entry);
|
||||
break;
|
||||
case LogLevel::Debug:
|
||||
emit debugAdded(entry);
|
||||
break;
|
||||
case LogLevel::Critical:
|
||||
emit criticalAdded(entry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LogManager::info(const QString& module, const QString& method,
|
||||
const QString& message)
|
||||
{
|
||||
log(LogLevel::Info, module, method, message);
|
||||
}
|
||||
|
||||
void LogManager::warning(const QString& module, const QString& method,
|
||||
const QString& message)
|
||||
{
|
||||
log(LogLevel::Warning, module, method, message);
|
||||
}
|
||||
|
||||
void LogManager::error(const QString& module, const QString& method,
|
||||
const QString& message)
|
||||
{
|
||||
log(LogLevel::Error, module, method, message);
|
||||
}
|
||||
|
||||
void LogManager::debug(const QString& module, const QString& method,
|
||||
const QString& message)
|
||||
{
|
||||
log(LogLevel::Debug, module, method, message);
|
||||
}
|
||||
|
||||
void LogManager::critical(const QString& module, const QString& method,
|
||||
const QString& message)
|
||||
{
|
||||
log(LogLevel::Critical, module, method, message);
|
||||
}
|
||||
|
||||
QList<LogEntry> LogManager::allEntries() const
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
return m_entries;
|
||||
}
|
||||
|
||||
QList<LogEntry> LogManager::entriesByTime(const QDateTime& from,
|
||||
const QDateTime& to) const
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
QList<LogEntry> result;
|
||||
|
||||
for (const auto& entry : m_entries) {
|
||||
if (entry.timestamp >= from && entry.timestamp <= to) {
|
||||
result.append(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QList<LogEntry> LogManager::entriesByLevel(LogLevel level) const
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
QList<LogEntry> result;
|
||||
|
||||
for (const auto& entry : m_entries) {
|
||||
if (entry.level == level) {
|
||||
result.append(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QList<LogEntry> LogManager::entriesByModule(const QString& module) const
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
QList<LogEntry> result;
|
||||
|
||||
for (const auto& entry : m_entries) {
|
||||
if (entry.module == module) {
|
||||
result.append(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QList<LogEntry> LogManager::search(const QString& text, bool caseSensitive) const
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
QList<LogEntry> result;
|
||||
|
||||
Qt::CaseSensitivity sensitivity = caseSensitive ?
|
||||
Qt::CaseSensitive : Qt::CaseInsensitive;
|
||||
|
||||
for (const auto& entry : m_entries) {
|
||||
if (entry.message.contains(text, sensitivity) ||
|
||||
entry.module.contains(text, sensitivity) ||
|
||||
entry.method.contains(text, sensitivity)) {
|
||||
result.append(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QList<LogEntry> LogManager::filter(const QList<LogLevel>& levels,
|
||||
const QString& moduleFilter,
|
||||
const QString& methodFilter) const
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
QList<LogEntry> result;
|
||||
|
||||
for (const auto& entry : m_entries) {
|
||||
// Проверка уровня
|
||||
if (!levels.contains(entry.level)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Проверка модуля
|
||||
if (!moduleFilter.isEmpty() && entry.module != moduleFilter) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Проверка метода
|
||||
if (!methodFilter.isEmpty() && entry.method != methodFilter) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.append(entry);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void LogManager::clear()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_entries.clear();
|
||||
locker.unlock();
|
||||
|
||||
emit logCleared();
|
||||
}
|
||||
|
||||
bool LogManager::saveToFile(const QString& filePath, bool append)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
|
||||
QFile file(filePath);
|
||||
QIODevice::OpenMode mode = QIODevice::WriteOnly | QIODevice::Text;
|
||||
if (append) {
|
||||
mode |= QIODevice::Append;
|
||||
}
|
||||
|
||||
if (!file.open(mode)) {
|
||||
emit fileError(QString("Не удалось открыть файл: %1").arg(file.errorString()));
|
||||
return false;
|
||||
}
|
||||
|
||||
QTextStream stream(&file);
|
||||
stream.setCodec("UTF-8");
|
||||
|
||||
// Заголовок
|
||||
stream << "Дата,Уровень,Модуль,Метод,Сообщение\n";
|
||||
|
||||
for (const auto& entry : m_entries) {
|
||||
stream << entry.toCsv() << "\n";
|
||||
}
|
||||
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LogManager::loadFromFile(const QString& filePath)
|
||||
{
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QTextStream stream(&file);
|
||||
stream.setCodec("UTF-8");
|
||||
|
||||
// Пропускаем заголовок
|
||||
QString header = stream.readLine();
|
||||
|
||||
QList<LogEntry> loadedEntries;
|
||||
|
||||
while (!stream.atEnd()) {
|
||||
QString line = stream.readLine();
|
||||
QStringList parts = line.split('"');
|
||||
|
||||
if (parts.size() < 9) continue;
|
||||
|
||||
LogEntry entry;
|
||||
entry.timestamp = QDateTime::fromString(parts[1], "yyyy-MM-dd hh:mm:ss");
|
||||
|
||||
QString levelStr = parts[3];
|
||||
if (levelStr == "INFO") entry.level = LogLevel::Info;
|
||||
else if (levelStr == "WARNING") entry.level = LogLevel::Warning;
|
||||
else if (levelStr == "ERROR") entry.level = LogLevel::Error;
|
||||
else if (levelStr == "DEBUG") entry.level = LogLevel::Debug;
|
||||
else if (levelStr == "CRITICAL") entry.level = LogLevel::Critical;
|
||||
|
||||
entry.module = parts[5];
|
||||
entry.method = parts[7];
|
||||
entry.message = parts[9];
|
||||
|
||||
loadedEntries.append(entry);
|
||||
}
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_entries = loadedEntries;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LogManager::exportToFormat(const QString& filePath, const QString& format)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QTextStream stream(&file);
|
||||
stream.setCodec("UTF-8");
|
||||
|
||||
if (format.toLower() == "html") {
|
||||
stream << "<!DOCTYPE html>\n<html>\n<head>\n";
|
||||
stream << "<meta charset=\"UTF-8\">\n";
|
||||
stream << "<title>Логи приложения</title>\n";
|
||||
stream << "<style>\n";
|
||||
stream << "body { font-family: monospace; }\n";
|
||||
stream << ".info { color: darkgreen; }\n";
|
||||
stream << ".warning { color: darkorange; }\n";
|
||||
stream << ".error { color: red; }\n";
|
||||
stream << ".debug { color: gray; }\n";
|
||||
stream << ".critical { color: darkred; }\n";
|
||||
stream << "</style>\n";
|
||||
stream << "</head>\n<body>\n";
|
||||
|
||||
for (const auto& entry : m_entries) {
|
||||
stream << entry.toHtml() << "\n";
|
||||
}
|
||||
|
||||
stream << "</body>\n</html>";
|
||||
|
||||
} else if (format.toLower() == "json") {
|
||||
QJsonArray array;
|
||||
for (const auto& entry : m_entries) {
|
||||
QJsonObject obj;
|
||||
obj["timestamp"] = entry.timestamp.toString(Qt::ISODate);
|
||||
obj["level"] = LogEntry::levelToString(entry.level);
|
||||
obj["module"] = entry.module;
|
||||
obj["method"] = entry.method;
|
||||
obj["message"] = entry.message;
|
||||
array.append(obj);
|
||||
}
|
||||
|
||||
QJsonDocument doc(array);
|
||||
stream << doc.toJson();
|
||||
|
||||
} else { // TXT (по умолчанию)
|
||||
for (const auto& entry : m_entries) {
|
||||
stream << entry.toString() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
QMap<LogLevel, int> LogManager::statistics() const
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
QMap<LogLevel, int> stats;
|
||||
|
||||
for (const auto& entry : m_entries) {
|
||||
stats[entry.level]++;
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
int LogManager::count() const
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
return m_entries.size();
|
||||
}
|
||||
|
||||
int LogManager::maxEntries() const
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
return m_maxEntries;
|
||||
}
|
||||
|
||||
void LogManager::setMaxEntries(int max)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_maxEntries = max;
|
||||
|
||||
// Удаляем лишние записи
|
||||
while (m_entries.size() > m_maxEntries) {
|
||||
m_entries.removeFirst();
|
||||
}
|
||||
}
|
||||
|
||||
LogSettings LogManager::settings() const
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
return m_settings;
|
||||
}
|
||||
|
||||
void LogManager::updateSettings(const LogSettings& newSettings)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
|
||||
// Закрываем старый файл, если изменился путь
|
||||
if (m_settings.logFilePath != newSettings.logFilePath && m_logFile.isOpen()) {
|
||||
m_logFile.close();
|
||||
}
|
||||
|
||||
m_settings = newSettings;
|
||||
|
||||
// Инициализируем файл
|
||||
if (m_settings.logToFile && !m_settings.logFilePath.isEmpty()) {
|
||||
if (!m_logFile.isOpen()) {
|
||||
m_logFile.setFileName(m_settings.logFilePath);
|
||||
if (!m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
|
||||
emit fileError(QString("Не удалось открыть файл логов: %1").arg(m_logFile.errorString()));
|
||||
} else {
|
||||
m_logStream.setDevice(&m_logFile);
|
||||
m_logStream.setCodec("UTF-8");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locker.unlock();
|
||||
emit settingsChanged();
|
||||
}
|
||||
|
||||
void LogManager::saveSettings(QSettings& settings)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
|
||||
settings.beginGroup("Logging");
|
||||
settings.setValue("logToFile", m_settings.logToFile);
|
||||
settings.setValue("logToConsole", m_settings.logToConsole);
|
||||
settings.setValue("logFilePath", m_settings.logFilePath);
|
||||
settings.setValue("maxFileSizeMB", m_settings.maxFileSizeMB);
|
||||
settings.setValue("maxFileCount", m_settings.maxFileCount);
|
||||
settings.setValue("showTimestamp", m_settings.showTimestamp);
|
||||
settings.setValue("showModule", m_settings.showModule);
|
||||
settings.setValue("showMethod", m_settings.showMethod);
|
||||
settings.setValue("maxEntries", m_maxEntries);
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
void LogManager::loadSettings(QSettings& settings)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
|
||||
settings.beginGroup("Logging");
|
||||
m_settings.logToFile = settings.value("logToFile", true).toBool();
|
||||
m_settings.logToConsole = settings.value("logToConsole", true).toBool();
|
||||
m_settings.logFilePath = settings.value("logFilePath", m_settings.logFilePath).toString();
|
||||
m_settings.maxFileSizeMB = settings.value("maxFileSizeMB", 10).toInt();
|
||||
m_settings.maxFileCount = settings.value("maxFileCount", 5).toInt();
|
||||
m_settings.showTimestamp = settings.value("showTimestamp", true).toBool();
|
||||
m_settings.showModule = settings.value("showModule", true).toBool();
|
||||
m_settings.showMethod = settings.value("showMethod", true).toBool();
|
||||
m_maxEntries = settings.value("maxEntries", 10000).toInt();
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
void LogManager::setLogToFileEnabled(bool enabled)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_settings.logToFile = enabled;
|
||||
|
||||
if (enabled && !m_logFile.isOpen()) {
|
||||
if (!m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
|
||||
emit fileError(QString("Не удалось открыть файл логов: %1").arg(m_logFile.errorString()));
|
||||
}
|
||||
} else if (!enabled && m_logFile.isOpen()) {
|
||||
m_logFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
void LogManager::setLogToConsoleEnabled(bool enabled)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_settings.logToConsole = enabled;
|
||||
}
|
||||
|
||||
void LogManager::setLogFilePath(const QString& path)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
|
||||
if (m_logFile.isOpen()) {
|
||||
m_logFile.close();
|
||||
}
|
||||
|
||||
m_settings.logFilePath = path;
|
||||
|
||||
if (m_settings.logToFile) {
|
||||
m_logFile.setFileName(path);
|
||||
if (!m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
|
||||
emit fileError(QString("Не удалось открыть файл логов: %1").arg(m_logFile.errorString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LogManager::setLevelColors(const QMap<LogLevel, QColor>& colors)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_settings.colors = colors;
|
||||
}
|
||||
|
||||
void LogManager::setLevelColor(LogLevel level, const QColor& color)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_settings.colors[level] = color;
|
||||
}
|
||||
|
||||
void LogManager::setFormat(bool showTimestamp, bool showModule, bool showMethod)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_settings.showTimestamp = showTimestamp;
|
||||
m_settings.showModule = showModule;
|
||||
m_settings.showMethod = showMethod;
|
||||
}
|
||||
|
||||
void LogManager::writeToFile(const LogEntry& entry)
|
||||
{
|
||||
if (!m_logFile.isOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString formatted = formatForFile(entry);
|
||||
m_logStream << formatted << "\n";
|
||||
m_logStream.flush();
|
||||
|
||||
// Проверяем размер файла
|
||||
if (checkFileSize()) {
|
||||
rotateLogFile();
|
||||
}
|
||||
}
|
||||
|
||||
void LogManager::rotateLogFile()
|
||||
{
|
||||
if (!m_logFile.isOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_logFile.close();
|
||||
|
||||
QString basePath = m_settings.logFilePath;
|
||||
QFileInfo fi(basePath);
|
||||
QString baseName = fi.baseName();
|
||||
QString suffix = fi.suffix();
|
||||
QString dir = fi.path();
|
||||
|
||||
// Удаляем самый старый файл
|
||||
QString oldestFile = QString("%1/%2.%3.%4")
|
||||
.arg(dir)
|
||||
.arg(baseName)
|
||||
.arg(m_settings.maxFileCount)
|
||||
.arg(suffix);
|
||||
|
||||
if (QFile::exists(oldestFile)) {
|
||||
QFile::remove(oldestFile);
|
||||
}
|
||||
|
||||
// Переименовываем остальные файлы
|
||||
for (int i = m_settings.maxFileCount - 1; i >= 1; i--) {
|
||||
QString oldFile = QString("%1/%2.%3.%4")
|
||||
.arg(dir)
|
||||
.arg(baseName)
|
||||
.arg(i)
|
||||
.arg(suffix);
|
||||
QString newFile = QString("%1/%2.%3.%4")
|
||||
.arg(dir)
|
||||
.arg(baseName)
|
||||
.arg(i + 1)
|
||||
.arg(suffix);
|
||||
|
||||
if (QFile::exists(oldFile)) {
|
||||
QFile::rename(oldFile, newFile);
|
||||
}
|
||||
}
|
||||
|
||||
// Переименовываем текущий файл
|
||||
QString firstBackup = QString("%1/%2.%3.%4")
|
||||
.arg(dir)
|
||||
.arg(baseName)
|
||||
.arg(1)
|
||||
.arg(suffix);
|
||||
|
||||
QFile::rename(basePath, firstBackup);
|
||||
|
||||
// Открываем новый файл
|
||||
if (!m_logFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
emit fileError(QString("Не удалось открыть файл логов после ротации: %1")
|
||||
.arg(m_logFile.errorString()));
|
||||
}
|
||||
}
|
||||
|
||||
bool LogManager::checkFileSize()
|
||||
{
|
||||
if (!m_logFile.isOpen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
qint64 size = m_logFile.size();
|
||||
qint64 maxSize = m_settings.maxFileSizeMB * 1024 * 1024;
|
||||
|
||||
return size > maxSize;
|
||||
}
|
||||
|
||||
QString LogManager::formatEntry(const LogEntry& entry) const
|
||||
{
|
||||
QStringList parts;
|
||||
|
||||
if (m_settings.showTimestamp) {
|
||||
parts.append(entry.timestamp.toString("hh:mm:ss"));
|
||||
}
|
||||
|
||||
parts.append(LogEntry::levelToString(entry.level));
|
||||
|
||||
if (m_settings.showModule && !entry.module.isEmpty()) {
|
||||
parts.append(entry.module);
|
||||
}
|
||||
|
||||
if (m_settings.showMethod && !entry.method.isEmpty()) {
|
||||
parts.append(entry.method);
|
||||
}
|
||||
|
||||
parts.append(entry.message);
|
||||
|
||||
return parts.join(" | ");
|
||||
}
|
||||
|
||||
QString LogManager::formatForConsole(const LogEntry& entry) const
|
||||
{
|
||||
QString formatted = formatEntry(entry);
|
||||
|
||||
// Добавляем цвет для консоли (если поддерживается)
|
||||
if (m_settings.colors.contains(entry.level)) {
|
||||
// Для консоли цвет добавляется через escape-последовательности
|
||||
QColor color = m_settings.colors[entry.level];
|
||||
// Это упрощенный вариант, можно расширить для реальной консоли
|
||||
return formatted;
|
||||
}
|
||||
|
||||
return formatted;
|
||||
}
|
||||
|
||||
QString LogManager::formatForFile(const LogEntry& entry) const
|
||||
{
|
||||
// Для файла используем CSV формат
|
||||
return entry.toCsv();
|
||||
}
|
||||
+394
@@ -0,0 +1,394 @@
|
||||
#ifndef LOGMANAGER_H
|
||||
#define LOGMANAGER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QDateTime>
|
||||
#include <QColor>
|
||||
#include <QList>
|
||||
#include <QMutex>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QSettings>
|
||||
|
||||
// ============================================================================
|
||||
// ПЕРЕЧИСЛЕНИЯ И СТРУКТУРЫ
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief Уровни логирования
|
||||
*/
|
||||
enum class LogLevel {
|
||||
Info = 0, // Информационные сообщения
|
||||
Warning = 1, // Предупреждения
|
||||
Error = 2, // Ошибки
|
||||
Debug = 3, // Отладочная информация
|
||||
Critical = 4 // Критические ошибки
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Настройки логирования
|
||||
*/
|
||||
struct LogSettings {
|
||||
bool logToFile = true; // Сохранять в файл
|
||||
bool logToConsole = true; // Выводить в консоль
|
||||
bool logToDatabase = false; // Сохранять в БД
|
||||
QString logFilePath = ""; // Путь к файлу логов
|
||||
int maxFileSizeMB = 10; // Максимальный размер файла (МБ)
|
||||
int maxFileCount = 5; // Максимальное количество файлов
|
||||
bool showTimestamp = true; // Показывать время
|
||||
bool showModule = true; // Показывать модуль
|
||||
bool showMethod = true; // Показывать метод
|
||||
QMap<LogLevel, QColor> colors; // Цвета для уровней
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Запись лога
|
||||
*/
|
||||
struct LogEntry {
|
||||
QDateTime timestamp; // Время записи
|
||||
LogLevel level; // Уровень
|
||||
QString module; // Модуль/компонент
|
||||
QString method; // Метод/функция
|
||||
QString message; // Сообщение
|
||||
qint64 threadId = 0; // ID потока
|
||||
QString sourceFile = ""; // Исходный файл
|
||||
int sourceLine = 0; // Строка в файле
|
||||
|
||||
// Методы для форматирования
|
||||
QString toString() const;
|
||||
QString toHtml() const;
|
||||
QString toCsv() const;
|
||||
QString toJson() const;
|
||||
|
||||
// Статические методы
|
||||
static QString levelToString(LogLevel level);
|
||||
static QColor levelToColor(LogLevel level);
|
||||
static QString formatDateTime(const QDateTime& dt);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// КЛАСС LOGMANAGER
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief Менеджер логирования приложения
|
||||
*/
|
||||
class LogManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
// ========================================================================
|
||||
// СИНГЛТОН
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* @brief Получить экземпляр менеджера (синглтон)
|
||||
*/
|
||||
static LogManager* instance();
|
||||
|
||||
/**
|
||||
* @brief Инициализировать менеджер
|
||||
* @param settings Настройки логирования
|
||||
*/
|
||||
static void initialize(const LogSettings& settings = LogSettings());
|
||||
|
||||
/**
|
||||
* @brief Очистить менеджер (освободить ресурсы)
|
||||
*/
|
||||
static void cleanup();
|
||||
|
||||
// ========================================================================
|
||||
// ОСНОВНОЙ ИНТЕРФЕЙС
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* @brief Добавить запись в лог
|
||||
* @param level Уровень логирования
|
||||
* @param module Модуль/компонент
|
||||
* @param method Метод/функция
|
||||
* @param message Сообщение
|
||||
*/
|
||||
void log(LogLevel level, const QString& module,
|
||||
const QString& method, const QString& message);
|
||||
|
||||
/**
|
||||
* @brief Добавить запись в лог с информацией об исходном коде
|
||||
*/
|
||||
void log(LogLevel level, const QString& module,
|
||||
const QString& method, const QString& message,
|
||||
const QString& sourceFile, int sourceLine);
|
||||
|
||||
// Быстрые методы для разных уровней
|
||||
void info(const QString& module, const QString& method,
|
||||
const QString& message);
|
||||
void warning(const QString& module, const QString& method,
|
||||
const QString& message);
|
||||
void error(const QString& module, const QString& method,
|
||||
const QString& message);
|
||||
void debug(const QString& module, const QString& method,
|
||||
const QString& message);
|
||||
void critical(const QString& module, const QString& method,
|
||||
const QString& message);
|
||||
|
||||
// ========================================================================
|
||||
// ФИЛЬТРАЦИЯ И ПОИСК
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* @brief Получить все записи
|
||||
*/
|
||||
QList<LogEntry> allEntries() const;
|
||||
|
||||
/**
|
||||
* @brief Получить записи за указанный период
|
||||
*/
|
||||
QList<LogEntry> entriesByTime(const QDateTime& from,
|
||||
const QDateTime& to) const;
|
||||
|
||||
/**
|
||||
* @brief Получить записи по уровню
|
||||
*/
|
||||
QList<LogEntry> entriesByLevel(LogLevel level) const;
|
||||
|
||||
/**
|
||||
* @brief Получить записи по модулю
|
||||
*/
|
||||
QList<LogEntry> entriesByModule(const QString& module) const;
|
||||
|
||||
/**
|
||||
* @brief Поиск по тексту сообщения
|
||||
*/
|
||||
QList<LogEntry> search(const QString& text, bool caseSensitive = false) const;
|
||||
|
||||
/**
|
||||
* @brief Применить фильтр
|
||||
*/
|
||||
QList<LogEntry> filter(const QList<LogLevel>& levels,
|
||||
const QString& moduleFilter = "",
|
||||
const QString& methodFilter = "") const;
|
||||
|
||||
// ========================================================================
|
||||
// УПРАВЛЕНИЕ
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* @brief Очистить все записи
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief Сохранить логи в файл
|
||||
*/
|
||||
bool saveToFile(const QString& filePath, bool append = false);
|
||||
|
||||
/**
|
||||
* @brief Загрузить логи из файла
|
||||
*/
|
||||
bool loadFromFile(const QString& filePath);
|
||||
|
||||
/**
|
||||
* @brief Экспортировать в формат
|
||||
*/
|
||||
bool exportToFormat(const QString& filePath, const QString& format = "txt");
|
||||
|
||||
/**
|
||||
* @brief Получить статистику
|
||||
*/
|
||||
QMap<LogLevel, int> statistics() const;
|
||||
|
||||
/**
|
||||
* @brief Получить количество записей
|
||||
*/
|
||||
int count() const;
|
||||
|
||||
/**
|
||||
* @brief Получить максимальное количество записей в памяти
|
||||
*/
|
||||
int maxEntries() const;
|
||||
|
||||
/**
|
||||
* @brief Установить максимальное количество записей в памяти
|
||||
*/
|
||||
void setMaxEntries(int max);
|
||||
|
||||
// ========================================================================
|
||||
// НАСТРОЙКИ
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* @brief Получить текущие настройки
|
||||
*/
|
||||
LogSettings settings() const;
|
||||
|
||||
/**
|
||||
* @brief Обновить настройки
|
||||
*/
|
||||
void updateSettings(const LogSettings& newSettings);
|
||||
|
||||
/**
|
||||
* @brief Сохранить настройки в конфиг
|
||||
*/
|
||||
void saveSettings(QSettings& settings);
|
||||
|
||||
/**
|
||||
* @brief Загрузить настройки из конфига
|
||||
*/
|
||||
void loadSettings(QSettings& settings);
|
||||
|
||||
// ========================================================================
|
||||
// УТИЛИТЫ
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* @brief Включить/выключить логирование в файл
|
||||
*/
|
||||
void setLogToFileEnabled(bool enabled);
|
||||
|
||||
/**
|
||||
* @brief Включить/выключить логирование в консоль
|
||||
*/
|
||||
void setLogToConsoleEnabled(bool enabled);
|
||||
|
||||
/**
|
||||
* @brief Установить путь к файлу логов
|
||||
*/
|
||||
void setLogFilePath(const QString& path);
|
||||
|
||||
/**
|
||||
* @brief Установить цвета для уровней
|
||||
*/
|
||||
void setLevelColors(const QMap<LogLevel, QColor>& colors);
|
||||
|
||||
/**
|
||||
* @brief Установить цвет для уровня
|
||||
*/
|
||||
void setLevelColor(LogLevel level, const QColor& color);
|
||||
|
||||
/**
|
||||
* @brief Установить формат вывода
|
||||
*/
|
||||
void setFormat(bool showTimestamp, bool showModule, bool showMethod);
|
||||
|
||||
signals:
|
||||
// ========================================================================
|
||||
// СИГНАЛЫ
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* @brief Добавлена новая запись в лог
|
||||
*/
|
||||
void entryAdded(const LogEntry& entry);
|
||||
|
||||
/**
|
||||
* @brief Логи очищены
|
||||
*/
|
||||
void logCleared();
|
||||
|
||||
/**
|
||||
* @brief Настройки изменены
|
||||
*/
|
||||
void settingsChanged();
|
||||
|
||||
/**
|
||||
* @brief Ошибка при записи в файл
|
||||
*/
|
||||
void fileError(const QString& error);
|
||||
|
||||
// Сигналы для конкретных уровней (для удобства)
|
||||
void infoAdded(const LogEntry& entry);
|
||||
void warningAdded(const LogEntry& entry);
|
||||
void errorAdded(const LogEntry& entry);
|
||||
void debugAdded(const LogEntry& entry);
|
||||
void criticalAdded(const LogEntry& entry);
|
||||
|
||||
private:
|
||||
// ========================================================================
|
||||
// ПРИВАТНЫЙ КОНСТРУКТОР (синглтон)
|
||||
// ========================================================================
|
||||
explicit LogManager(QObject* parent = nullptr);
|
||||
~LogManager();
|
||||
|
||||
// ========================================================================
|
||||
// ПРИВАТНЫЕ МЕТОДЫ
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* @brief Записать в файл
|
||||
*/
|
||||
void writeToFile(const LogEntry& entry);
|
||||
|
||||
/**
|
||||
* @brief Ротация лог-файлов
|
||||
*/
|
||||
void rotateLogFile();
|
||||
|
||||
/**
|
||||
* @brief Проверить размер файла
|
||||
*/
|
||||
bool checkFileSize();
|
||||
|
||||
/**
|
||||
* @brief Форматирование записи для вывода
|
||||
*/
|
||||
QString formatEntry(const LogEntry& entry) const;
|
||||
|
||||
/**
|
||||
* @brief Форматирование для консоли
|
||||
*/
|
||||
QString formatForConsole(const LogEntry& entry) const;
|
||||
|
||||
/**
|
||||
* @brief Форматирование для файла
|
||||
*/
|
||||
QString formatForFile(const LogEntry& entry) const;
|
||||
|
||||
// ========================================================================
|
||||
// ЧЛЕНЫ КЛАССА
|
||||
// ========================================================================
|
||||
|
||||
static LogManager* m_instance; // Экземпляр синглтона
|
||||
QList<LogEntry> m_entries; // Список записей
|
||||
LogSettings m_settings; // Настройки
|
||||
mutable QMutex m_mutex; // Мьютекс для потокобезопасности
|
||||
QFile m_logFile; // Файл логов
|
||||
QTextStream m_logStream; // Поток для записи в файл
|
||||
int m_maxEntries = 10000; // Максимальное количество записей
|
||||
|
||||
// Предварительное объявление для Q_DISABLE_COPY
|
||||
LogManager(const LogManager&) = delete;
|
||||
LogManager& operator=(const LogManager&) = delete;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// МАКРОСЫ ДЛЯ УДОБСТВА
|
||||
// ============================================================================
|
||||
|
||||
// Базовый макрос
|
||||
#define LOG(level, module, method, message) \
|
||||
LogManager::instance()->log(level, module, method, message, __FILE__, __LINE__)
|
||||
|
||||
// Макросы для уровней
|
||||
#define LOG_INFO(module, method, message) \
|
||||
LOG(LogLevel::Info, module, method, message)
|
||||
|
||||
#define LOG_WARNING(module, method, message) \
|
||||
LOG(LogLevel::Warning, module, method, message)
|
||||
|
||||
#define LOG_ERROR(module, method, message) \
|
||||
LOG(LogLevel::Error, module, method, message)
|
||||
|
||||
#define LOG_DEBUG(module, method, message) \
|
||||
LOG(LogLevel::Debug, module, method, message)
|
||||
|
||||
#define LOG_CRITICAL(module, method, message) \
|
||||
LOG(LogLevel::Critical, module, method, message)
|
||||
|
||||
// Макрос для проверки на отладку
|
||||
#ifdef QT_DEBUG
|
||||
#define LOG_DEBUG_ONLY(module, method, message) LOG_DEBUG(module, method, message)
|
||||
#else
|
||||
#define LOG_DEBUG_ONLY(module, method, message) Q_UNUSED(module); Q_UNUSED(method); Q_UNUSED(message)
|
||||
#endif
|
||||
|
||||
#endif // LOGMANAGER_H
|
||||
@@ -0,0 +1,101 @@
|
||||
#include "MediaFileManager.h"
|
||||
#include <QDebug>
|
||||
|
||||
MediaFileManager::MediaFileManager()
|
||||
{
|
||||
// Конструктор может быть использован для инициализации
|
||||
}
|
||||
|
||||
bool MediaFileManager::addFile(const QString& name, const QString& filePath)
|
||||
{
|
||||
// Проверка на пустые значения
|
||||
if (name.isEmpty() || filePath.isEmpty()) {
|
||||
qWarning() << "Имя файла или путь не могут быть пустыми";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Проверка на уникальность имени
|
||||
if (contains(name)) {
|
||||
qWarning() << "Файл с именем" << name << "уже существует";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Добавление нового файла
|
||||
mediaFiles.append(MediaFile(name, filePath));
|
||||
qDebug() << "Файл" << name << "добавлен с путем:" << filePath;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MediaFileManager::removeFile(const QString& name)
|
||||
{
|
||||
int index = findFileIndex(name);
|
||||
|
||||
if (index == -1) {
|
||||
qWarning() << "Файл с именем" << name << "не найден";
|
||||
return false;
|
||||
}
|
||||
|
||||
mediaFiles.remove(index);
|
||||
qDebug() << "Файл" << name << "удален";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MediaFileManager::updateFile(const QString& oldName, const QString& newName, const QString& newFilePath)
|
||||
{
|
||||
int index = findFileIndex(oldName);
|
||||
|
||||
if (index == -1) {
|
||||
qWarning() << "Файл с именем" << oldName << "не найден";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Проверяем, не используется ли новое имя другим файлом
|
||||
if (oldName != newName && contains(newName)) {
|
||||
qWarning() << "Файл с именем" << newName << "уже существует";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Обновляем информацию о файле
|
||||
mediaFiles[index].name = newName;
|
||||
mediaFiles[index].filePath = newFilePath;
|
||||
|
||||
qDebug() << "Файл обновлен:" << oldName << "->" << newName << "путь:" << newFilePath;
|
||||
return true;
|
||||
}
|
||||
|
||||
QString MediaFileManager::getFilePathByName(const QString& name) const
|
||||
{
|
||||
int index = findFileIndex(name);
|
||||
|
||||
if (index == -1) {
|
||||
qWarning() << "Файл с именем" << name << "не найден";
|
||||
return QString();
|
||||
}
|
||||
|
||||
return mediaFiles[index].filePath;
|
||||
}
|
||||
|
||||
int MediaFileManager::getFileCount() const
|
||||
{
|
||||
return mediaFiles.size();
|
||||
}
|
||||
|
||||
QVector<MediaFile> MediaFileManager::getAllFiles() const
|
||||
{
|
||||
return mediaFiles;
|
||||
}
|
||||
|
||||
bool MediaFileManager::contains(const QString& name) const
|
||||
{
|
||||
return findFileIndex(name) != -1;
|
||||
}
|
||||
|
||||
int MediaFileManager::findFileIndex(const QString& name) const
|
||||
{
|
||||
for (int i = 0; i < mediaFiles.size(); ++i) {
|
||||
if (mediaFiles[i].name == name) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
#ifndef MEDIAFILEMANAGER_H
|
||||
#define MEDIAFILEMANAGER_H
|
||||
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
// Структура для хранения информации о медиафайле
|
||||
struct MediaFile {
|
||||
QString name; // Имя файла
|
||||
QString filePath; // Полный путь к файлу
|
||||
|
||||
MediaFile() = default;
|
||||
MediaFile(const QString& name, const QString& filePath)
|
||||
: name(name), filePath(filePath) {}
|
||||
};
|
||||
|
||||
class MediaFileManager
|
||||
{
|
||||
public:
|
||||
MediaFileManager();
|
||||
|
||||
// Добавление файла
|
||||
bool addFile(const QString& name, const QString& filePath);
|
||||
|
||||
// Удаление файла по имени
|
||||
bool removeFile(const QString& name);
|
||||
|
||||
// Изменение информации о файле
|
||||
bool updateFile(const QString& oldName, const QString& newName, const QString& newFilePath);
|
||||
|
||||
// Получение пути к файлу по имени
|
||||
QString getFilePathByName(const QString& name) const;
|
||||
|
||||
// Получение количества файлов
|
||||
int getFileCount() const;
|
||||
|
||||
// Получение всех файлов (для отладки или отображения)
|
||||
QVector<MediaFile> getAllFiles() const;
|
||||
|
||||
// Проверка существования файла по имени
|
||||
bool contains(const QString& name) const;
|
||||
|
||||
private:
|
||||
QVector<MediaFile> mediaFiles;
|
||||
|
||||
// Поиск индекса файла по имени
|
||||
int findFileIndex(const QString& name) const;
|
||||
};
|
||||
|
||||
#endif // MEDIAFILEMANAGER_H
|
||||
@@ -1,12 +1,18 @@
|
||||
debug/commandprocessor.o
|
||||
debug/emoteprovider.o
|
||||
debug/fcolorsetting.o
|
||||
debug/fcreatechat.o
|
||||
debug/fcreatenotify.o
|
||||
debug/ffontsetting.o
|
||||
debug/filemanager.o
|
||||
debug/fsettingsws.o
|
||||
debug/fsinglegrid.o
|
||||
debug/logmanager.o
|
||||
debug/main.o
|
||||
debug/mediafilemanager.o
|
||||
debug/neuralnetworkmanager.o
|
||||
debug/randommanager.o
|
||||
debug/randomresponses.o
|
||||
debug/soundmanager.o
|
||||
debug/tauth.o
|
||||
debug/ttw_api.o
|
||||
@@ -20,6 +26,7 @@ debug/userwidget.o
|
||||
debug/webserverchat.o
|
||||
debug/webservernotify.o
|
||||
debug/websocketclient.o
|
||||
debug/moc_commandprocessor.o
|
||||
debug/moc_emoteprovider.o
|
||||
debug/moc_fcolorsetting.o
|
||||
debug/moc_fcreatechat.o
|
||||
@@ -27,7 +34,10 @@ debug/moc_fcreatenotify.o
|
||||
debug/moc_ffontsetting.o
|
||||
debug/moc_fsettingsws.o
|
||||
debug/moc_fsinglegrid.o
|
||||
debug/moc_logmanager.o
|
||||
debug/moc_neuralnetworkmanager.o
|
||||
debug/moc_randommanager.o
|
||||
debug/moc_randomresponses.o
|
||||
debug/moc_soundmanager.o
|
||||
debug/moc_tauth.o
|
||||
debug/moc_ttw_api.o
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
release/commandprocessor.o
|
||||
release/emoteprovider.o
|
||||
release/fcolorsetting.o
|
||||
release/fcreatechat.o
|
||||
release/fcreatenotify.o
|
||||
release/ffontsetting.o
|
||||
release/filemanager.o
|
||||
release/fsettingsws.o
|
||||
release/fsinglegrid.o
|
||||
release/logmanager.o
|
||||
release/main.o
|
||||
release/mediafilemanager.o
|
||||
release/neuralnetworkmanager.o
|
||||
release/randommanager.o
|
||||
release/randomresponses.o
|
||||
release/soundmanager.o
|
||||
release/tauth.o
|
||||
release/ttw_api.o
|
||||
@@ -20,6 +26,7 @@ release/userwidget.o
|
||||
release/webserverchat.o
|
||||
release/webservernotify.o
|
||||
release/websocketclient.o
|
||||
release/moc_commandprocessor.o
|
||||
release/moc_emoteprovider.o
|
||||
release/moc_fcolorsetting.o
|
||||
release/moc_fcreatechat.o
|
||||
@@ -27,7 +34,10 @@ release/moc_fcreatenotify.o
|
||||
release/moc_ffontsetting.o
|
||||
release/moc_fsettingsws.o
|
||||
release/moc_fsinglegrid.o
|
||||
release/moc_logmanager.o
|
||||
release/moc_neuralnetworkmanager.o
|
||||
release/moc_randommanager.o
|
||||
release/moc_randomresponses.o
|
||||
release/moc_soundmanager.o
|
||||
release/moc_tauth.o
|
||||
release/moc_ttw_api.o
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
#include "randommanager.h"
|
||||
#include <QTableWidget>
|
||||
#include <QDebug>
|
||||
|
||||
RandomManager::RandomManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_database(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
RandomManager::~RandomManager()
|
||||
{
|
||||
// Сохраняем данные при уничтожении
|
||||
if (m_database) {
|
||||
saveToDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
bool RandomManager::initialize(uDataBase *database)
|
||||
{
|
||||
if (!database || !database->isConnected()) {
|
||||
qWarning() << "RandomManager: База данных не подключена";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_database = database;
|
||||
return loadFromDatabase();
|
||||
}
|
||||
|
||||
bool RandomManager::addRange(const QString &name, int startValue, int endValue)
|
||||
{
|
||||
// Проверка входных данных
|
||||
if (name.isEmpty()) {
|
||||
qWarning() << "RandomManager: Нельзя добавить диапазон с пустым именем";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isValidRange(startValue, endValue)) {
|
||||
qWarning() << "RandomManager: Некорректный диапазон:" << startValue << "-" << endValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Проверка на дубликат
|
||||
if (contains(name)) {
|
||||
qWarning() << "RandomManager: Диапазон с именем" << name << "уже существует";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Добавляем в список
|
||||
RandomStruct newRange(name, startValue, endValue);
|
||||
m_ranges.append(newRange);
|
||||
|
||||
// Сохраняем в БД
|
||||
if (!saveToDatabase()) {
|
||||
m_ranges.removeLast(); // Откатываем если не удалось сохранить
|
||||
return false;
|
||||
}
|
||||
|
||||
emit rangeAdded(name, startValue, endValue);
|
||||
emit dataChanged();
|
||||
|
||||
qDebug() << "RandomManager: Добавлен диапазон" << name << startValue << "-" << endValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RandomManager::removeRange(const QString &name)
|
||||
{
|
||||
int index = findIndex(name);
|
||||
if (index == -1) {
|
||||
qWarning() << "RandomManager: Диапазон" << name << "не найден";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Сохраняем данные для сигнала
|
||||
RandomStruct removedRange = m_ranges.at(index);
|
||||
|
||||
// Удаляем из списка
|
||||
m_ranges.removeAt(index);
|
||||
|
||||
// Сохраняем в БД
|
||||
if (!saveToDatabase()) {
|
||||
// Откатываем изменения
|
||||
m_ranges.insert(index, removedRange);
|
||||
return false;
|
||||
}
|
||||
|
||||
emit rangeRemoved(name);
|
||||
emit dataChanged();
|
||||
|
||||
qDebug() << "RandomManager: Удален диапазон" << name;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RandomManager::updateRange(const QString &oldName, const QString &newName,
|
||||
int startValue, int endValue)
|
||||
{
|
||||
int index = findIndex(oldName);
|
||||
if (index == -1) {
|
||||
qWarning() << "RandomManager: Диапазон" << oldName << "не найден";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isValidRange(startValue, endValue)) {
|
||||
qWarning() << "RandomManager: Некорректный диапазон:" << startValue << "-" << endValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Проверяем, не занято ли новое имя другим диапазоном (если имя изменилось)
|
||||
if (oldName != newName && contains(newName)) {
|
||||
qWarning() << "RandomManager: Имя" << newName << "уже занято";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Сохраняем старые данные для отката
|
||||
RandomStruct oldRange = m_ranges.at(index);
|
||||
|
||||
// Обновляем
|
||||
m_ranges[index].name = newName;
|
||||
m_ranges[index].startValue = startValue;
|
||||
m_ranges[index].endValue = endValue;
|
||||
|
||||
// Сохраняем в БД
|
||||
if (!saveToDatabase()) {
|
||||
// Откатываем изменения
|
||||
m_ranges[index] = oldRange;
|
||||
return false;
|
||||
}
|
||||
|
||||
emit rangeUpdated(oldName, newName);
|
||||
emit dataChanged();
|
||||
|
||||
qDebug() << "RandomManager: Обновлен диапазон" << oldName << "->" << newName
|
||||
<< startValue << "-" << endValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RandomManager::contains(const QString &name) const
|
||||
{
|
||||
return findIndex(name) != -1;
|
||||
}
|
||||
|
||||
int RandomManager::getRandomValue(const QString &name) const
|
||||
{
|
||||
int index = findIndex(name);
|
||||
if (index == -1) {
|
||||
qWarning() << "RandomManager: Диапазон" << name << "не найден";
|
||||
return 0;
|
||||
}
|
||||
|
||||
const RandomStruct &range = m_ranges.at(index);
|
||||
if (range.startValue == range.endValue) {
|
||||
return range.startValue;
|
||||
}
|
||||
|
||||
int min = qMin(range.startValue, range.endValue);
|
||||
int max = qMax(range.startValue, range.endValue);
|
||||
|
||||
return QRandomGenerator::global()->bounded(min, max + 1);
|
||||
}
|
||||
|
||||
QPair<int, int> RandomManager::getRange(const QString &name) const
|
||||
{
|
||||
int index = findIndex(name);
|
||||
if (index == -1) {
|
||||
return qMakePair(0, 0);
|
||||
}
|
||||
|
||||
const RandomStruct &range = m_ranges.at(index);
|
||||
return qMakePair(range.startValue, range.endValue);
|
||||
}
|
||||
|
||||
bool RandomManager::saveToDatabase()
|
||||
{
|
||||
if (!m_database) {
|
||||
qWarning() << "RandomManager: База данных не установлена";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Создаем временную таблицу для сохранения
|
||||
QTableWidget tempTable;
|
||||
|
||||
// Настраиваем таблицу
|
||||
QStringList headers;
|
||||
headers << "Имя" << "От" << "До";
|
||||
tempTable.setColumnCount(headers.size());
|
||||
tempTable.setHorizontalHeaderLabels(headers);
|
||||
|
||||
// Заполняем таблицу данными
|
||||
tempTable.setRowCount(m_ranges.size());
|
||||
|
||||
for (int i = 0; i < m_ranges.size(); ++i) {
|
||||
const RandomStruct &range = m_ranges.at(i);
|
||||
|
||||
// Имя
|
||||
QTableWidgetItem *nameItem = new QTableWidgetItem(range.name);
|
||||
tempTable.setItem(i, 0, nameItem);
|
||||
|
||||
// Начальное значение
|
||||
QTableWidgetItem *startItem = new QTableWidgetItem(QString::number(range.startValue));
|
||||
startItem->setTextAlignment(Qt::AlignCenter);
|
||||
tempTable.setItem(i, 1, startItem);
|
||||
|
||||
// Конечное значение
|
||||
QTableWidgetItem *endItem = new QTableWidgetItem(QString::number(range.endValue));
|
||||
endItem->setTextAlignment(Qt::AlignCenter);
|
||||
tempTable.setItem(i, 2, endItem);
|
||||
}
|
||||
|
||||
// Сохраняем в БД
|
||||
// Используем метод SaveTableWidget, который сохраняет данные в соответствующую таблицу
|
||||
m_database->SaveTableWidget(&tempTable);
|
||||
|
||||
qDebug() << "RandomManager: Сохранено" << m_ranges.size() << "диапазонов в БД";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RandomManager::loadFromDatabase()
|
||||
{
|
||||
if (!m_database) {
|
||||
qWarning() << "RandomManager: База данных не установлена";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Создаем временную таблицу для загрузки
|
||||
QTableWidget tempTable;
|
||||
|
||||
// Настраиваем таблицу
|
||||
QStringList headers;
|
||||
headers << "Имя" << "От" << "До";
|
||||
tempTable.setColumnCount(headers.size());
|
||||
tempTable.setHorizontalHeaderLabels(headers);
|
||||
|
||||
// Загружаем данные из БД
|
||||
m_database->LoadTableWidget(&tempTable);
|
||||
|
||||
// Очищаем текущий список
|
||||
m_ranges.clear();
|
||||
|
||||
// Заполняем список из таблицы
|
||||
for (int row = 0; row < tempTable.rowCount(); ++row) {
|
||||
QTableWidgetItem *nameItem = tempTable.item(row, 0);
|
||||
QTableWidgetItem *startItem = tempTable.item(row, 1);
|
||||
QTableWidgetItem *endItem = tempTable.item(row, 2);
|
||||
|
||||
if (!nameItem || !startItem || !endItem) {
|
||||
qWarning() << "RandomManager: Пропущена строка" << row << "из-за отсутствия данных";
|
||||
continue;
|
||||
}
|
||||
|
||||
QString name = nameItem->text().trimmed();
|
||||
if (name.isEmpty()) {
|
||||
qWarning() << "RandomManager: Пропущена строка" << row << "с пустым именем";
|
||||
continue;
|
||||
}
|
||||
|
||||
bool okStart, okEnd;
|
||||
int startValue = startItem->text().toInt(&okStart);
|
||||
int endValue = endItem->text().toInt(&okEnd);
|
||||
|
||||
if (!okStart || !okEnd) {
|
||||
qWarning() << "RandomManager: Некорректные числовые значения в строке" << row;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isValidRange(startValue, endValue)) {
|
||||
qWarning() << "RandomManager: Некорректный диапазон в строке" << row
|
||||
<< startValue << "-" << endValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
m_ranges.append(RandomStruct(name, startValue, endValue));
|
||||
}
|
||||
|
||||
qDebug() << "RandomManager: Загружено" << m_ranges.size() << "диапазонов из БД";
|
||||
emit dataChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RandomManager::isValidRange(int start, int end) const
|
||||
{
|
||||
// Допускаются любые целые числа, даже если start > end
|
||||
// Мы будем использовать qMin и qMax при генерации случайного числа
|
||||
return true;
|
||||
}
|
||||
|
||||
int RandomManager::findIndex(const QString &name) const
|
||||
{
|
||||
for (int i = 0; i < m_ranges.size(); ++i) {
|
||||
if (m_ranges.at(i).name == name) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#ifndef RANDOMMANAGER_H
|
||||
#define RANDOMMANAGER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QRandomGenerator>
|
||||
#include "udatabase.h"
|
||||
|
||||
class RandomManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
|
||||
|
||||
public:
|
||||
struct RandomStruct {
|
||||
QString name;
|
||||
int startValue;
|
||||
int endValue;
|
||||
|
||||
RandomStruct() : startValue(0), endValue(0) {}
|
||||
RandomStruct(const QString &name, int start, int end)
|
||||
: name(name), startValue(start), endValue(end) {}
|
||||
|
||||
bool operator==(const QString &otherName) const { return name == otherName; }
|
||||
};
|
||||
|
||||
explicit RandomManager(QObject *parent = nullptr);
|
||||
~RandomManager();
|
||||
|
||||
// Основные методы
|
||||
bool initialize(uDataBase *database);
|
||||
|
||||
// Работа с диапазонами
|
||||
bool addRange(const QString &name, int startValue, int endValue);
|
||||
bool removeRange(const QString &name);
|
||||
bool updateRange(const QString &oldName, const QString &newName, int startValue, int endValue);
|
||||
|
||||
// Получение данных
|
||||
QList<RandomStruct> getAllRanges() const { return m_ranges; }
|
||||
bool contains(const QString &name) const;
|
||||
int getRandomValue(const QString &name) const;
|
||||
QPair<int, int> getRange(const QString &name) const;
|
||||
|
||||
// Сохранение/загрузка
|
||||
bool saveToDatabase();
|
||||
bool loadFromDatabase();
|
||||
|
||||
// Валидация
|
||||
bool isValidRange(int start, int end) const;
|
||||
|
||||
signals:
|
||||
void dataChanged();
|
||||
void rangeAdded(const QString &name, int start, int end);
|
||||
void rangeRemoved(const QString &name);
|
||||
void rangeUpdated(const QString &oldName, const QString &newName);
|
||||
|
||||
private:
|
||||
uDataBase *m_database;
|
||||
QList<RandomStruct> m_ranges;
|
||||
|
||||
int findIndex(const QString &name) const;
|
||||
};
|
||||
|
||||
#endif // RANDOMMANAGER_H
|
||||
@@ -0,0 +1,103 @@
|
||||
#include "RandomResponses.h"
|
||||
#include <QDebug>
|
||||
|
||||
RandomResponses::RandomResponses(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_randomGenerator(QRandomGenerator::global()->generate())
|
||||
{
|
||||
}
|
||||
|
||||
QString RandomResponses::getResponse(const QString &groupName)
|
||||
{
|
||||
// Проверяем существование группы
|
||||
if (!m_groups.contains(groupName)) {
|
||||
qWarning() << "Группа" << groupName << "не найдена";
|
||||
return QString();
|
||||
}
|
||||
|
||||
const ResponseGroup &group = m_groups[groupName];
|
||||
|
||||
// Проверяем наличие ответов в группе
|
||||
if (group.responses.isEmpty()) {
|
||||
qWarning() << "Группа" << groupName << "не содержит ответов";
|
||||
return QString();
|
||||
}
|
||||
|
||||
// Генерируем случайный индекс
|
||||
int randomIndex = m_randomGenerator.bounded(group.responses.size());
|
||||
|
||||
// Возвращаем случайный ответ
|
||||
return group.responses.at(randomIndex);
|
||||
}
|
||||
|
||||
bool RandomResponses::addResponse(const QString &groupName, const QString &response)
|
||||
{
|
||||
if (response.isEmpty()) {
|
||||
qWarning() << "Пустой ответ не может быть добавлен";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Если группа существует, добавляем ответ в существующую
|
||||
if (m_groups.contains(groupName)) {
|
||||
m_groups[groupName].responses.append(response);
|
||||
}
|
||||
// Иначе создаем новую группу
|
||||
else {
|
||||
ResponseGroup newGroup;
|
||||
newGroup.name = groupName;
|
||||
newGroup.responses.append(response);
|
||||
m_groups.insert(groupName, newGroup);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RandomResponses::removeResponse(const QString &groupName, const QString &response)
|
||||
{
|
||||
// Проверяем существование группы
|
||||
if (!m_groups.contains(groupName)) {
|
||||
qWarning() << "Группа" << groupName << "не найдена";
|
||||
return false;
|
||||
}
|
||||
|
||||
ResponseGroup &group = m_groups[groupName];
|
||||
|
||||
// Удаляем все вхождения ответа
|
||||
int removedCount = group.responses.removeAll(response);
|
||||
|
||||
// Если группа стала пустой, удаляем ее
|
||||
if (group.responses.isEmpty()) {
|
||||
m_groups.remove(groupName);
|
||||
}
|
||||
|
||||
return removedCount > 0;
|
||||
}
|
||||
|
||||
bool RandomResponses::removeGroup(const QString &groupName)
|
||||
{
|
||||
return m_groups.remove(groupName) > 0;
|
||||
}
|
||||
|
||||
QStringList RandomResponses::getGroupNames() const
|
||||
{
|
||||
return m_groups.keys();
|
||||
}
|
||||
|
||||
QStringList RandomResponses::getGroupResponses(const QString &groupName) const
|
||||
{
|
||||
if (m_groups.contains(groupName)) {
|
||||
return m_groups[groupName].responses;
|
||||
}
|
||||
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
bool RandomResponses::hasGroup(const QString &groupName) const
|
||||
{
|
||||
return m_groups.contains(groupName);
|
||||
}
|
||||
|
||||
void RandomResponses::clear()
|
||||
{
|
||||
m_groups.clear();
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#ifndef RANDOMRESPONSES_H
|
||||
#define RANDOMRESPONSES_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QMap>
|
||||
#include <QList>
|
||||
#include <QVector>
|
||||
#include <QRandomGenerator>
|
||||
|
||||
// Структура для хранения группы ответов
|
||||
struct ResponseGroup {
|
||||
QString name; // Имя группы
|
||||
QStringList responses; // Список возможных ответов
|
||||
};
|
||||
|
||||
class RandomResponses : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RandomResponses(QObject *parent = nullptr);
|
||||
|
||||
// Получить случайный ответ из группы
|
||||
QString getResponse(const QString &groupName);
|
||||
|
||||
// Добавить группу или дополнить существующую
|
||||
bool addResponse(const QString &groupName, const QString &response);
|
||||
|
||||
// Удалить конкретный ответ из группы
|
||||
bool removeResponse(const QString &groupName, const QString &response);
|
||||
|
||||
// Удалить всю группу
|
||||
bool removeGroup(const QString &groupName);
|
||||
|
||||
// Получить список всех групп
|
||||
QStringList getGroupNames() const;
|
||||
|
||||
// Получить все ответы группы
|
||||
QStringList getGroupResponses(const QString &groupName) const;
|
||||
|
||||
// Проверить существование группы
|
||||
bool hasGroup(const QString &groupName) const;
|
||||
|
||||
// Очистить все данные
|
||||
void clear();
|
||||
|
||||
private:
|
||||
QMap<QString, ResponseGroup> m_groups; // Хранение групп по имени
|
||||
QRandomGenerator m_randomGenerator; // Генератор случайных чисел
|
||||
};
|
||||
|
||||
#endif // RANDOMRESPONSES_H
|
||||
+2
-2
@@ -323,7 +323,7 @@ void TTwAPI::banUserTime(const QString &id, int timeMinutes)
|
||||
toLog(2, "TTwAPI::banUserTime", "Не удалось получить botId");
|
||||
return;
|
||||
}
|
||||
|
||||
toLog(2, "TTwAPI::баним", id);
|
||||
QJsonObject innerData;
|
||||
innerData["user_id"] = id;
|
||||
innerData["duration"] = timeMinutes * 60;
|
||||
@@ -503,7 +503,7 @@ void TTwAPI::toLog(int level, const QString &method, const QString &message)
|
||||
Q_UNUSED(level);
|
||||
Q_UNUSED(method);
|
||||
Q_UNUSED(message);
|
||||
|
||||
qDebug() << message;
|
||||
// uGeneral.toLog("ttw_api", method, message, level);
|
||||
}
|
||||
|
||||
|
||||
+222
@@ -1179,3 +1179,225 @@ bool uDataBase::updateChat(const QString &name, HttpServerChat *server,
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool uDataBase::createNotificationsTable()
|
||||
{
|
||||
QSqlQuery query(m_db);
|
||||
QString sql =
|
||||
"CREATE TABLE IF NOT EXISTS notifications ("
|
||||
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||
" name TEXT NOT NULL,"
|
||||
" type TEXT NOT NULL," // "notification"
|
||||
" port INTEGER NOT NULL,"
|
||||
" block_color TEXT,"
|
||||
" border_color TEXT,"
|
||||
" border_size INTEGER,"
|
||||
" transparency INTEGER,"
|
||||
" page_background_color TEXT,"
|
||||
" title_family TEXT,"
|
||||
" title_size INTEGER,"
|
||||
" title_color TEXT,"
|
||||
" content_family TEXT,"
|
||||
" content_size INTEGER,"
|
||||
" content_color TEXT,"
|
||||
" duration INTEGER,"
|
||||
" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
|
||||
")";
|
||||
|
||||
if (!query.exec(sql)) {
|
||||
m_lastError = query.lastError().text();
|
||||
qWarning() << "Failed to create notifications table:" << m_lastError;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Создаем индекс для быстрого поиска по порту
|
||||
sql = "CREATE INDEX IF NOT EXISTS idx_notifications_port ON notifications (port)";
|
||||
query.exec(sql);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool uDataBase::saveNotification(const QString &name, HttpServer *server)
|
||||
{
|
||||
if (!server) {
|
||||
m_lastError = "Server is null";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_db.isOpen()) {
|
||||
m_lastError = "Database is not open";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Создаем таблицу если не существует
|
||||
if (!tableExists("notifications")) {
|
||||
if (!createNotificationsTable()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QSqlQuery query(m_db);
|
||||
query.prepare(
|
||||
"INSERT INTO notifications ("
|
||||
" name, type, port, block_color, border_color, border_size,"
|
||||
" transparency, page_background_color, title_family, title_size,"
|
||||
" title_color, content_family, content_size, content_color, duration"
|
||||
") VALUES ("
|
||||
" :name, :type, :port, :block_color, :border_color, :border_size,"
|
||||
" :transparency, :page_background_color, :title_family, :title_size,"
|
||||
" :title_color, :content_family, :content_size, :content_color, :duration"
|
||||
")"
|
||||
);
|
||||
|
||||
// Используем реальные значения из сервера
|
||||
query.bindValue(":name", name);
|
||||
query.bindValue(":type", "notification");
|
||||
query.bindValue(":port", server->port());
|
||||
query.bindValue(":block_color", server->getBlockColor());
|
||||
query.bindValue(":border_color", server->getBorderColor());
|
||||
query.bindValue(":border_size", server->getBorderSize());
|
||||
query.bindValue(":transparency", server->getTransparency());
|
||||
query.bindValue(":page_background_color", server->getPageBackgroundColor());
|
||||
query.bindValue(":title_family", server->getTitleFamily());
|
||||
query.bindValue(":title_size", server->getTitleSize());
|
||||
query.bindValue(":title_color", server->getTitleColor());
|
||||
query.bindValue(":content_family", server->getContentFamily());
|
||||
query.bindValue(":content_size", server->getContentSize());
|
||||
query.bindValue(":content_color", server->getContentColor());
|
||||
query.bindValue(":duration", server->getDuration());
|
||||
|
||||
if (!query.exec()) {
|
||||
m_lastError = query.lastError().text();
|
||||
qWarning() << "Failed to save notification:" << m_lastError;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool uDataBase::updateNotification(const QString &name, HttpServer *server, int oldPort)
|
||||
{
|
||||
if (!server) {
|
||||
m_lastError = "Server is null";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_db.isOpen()) {
|
||||
m_lastError = "Database is not open";
|
||||
return false;
|
||||
}
|
||||
|
||||
QSqlQuery query(m_db);
|
||||
query.prepare(
|
||||
"UPDATE notifications SET "
|
||||
" name = :name, "
|
||||
" port = :port, "
|
||||
" block_color = :block_color, "
|
||||
" border_color = :border_color, "
|
||||
" border_size = :border_size, "
|
||||
" transparency = :transparency, "
|
||||
" page_background_color = :page_background_color, "
|
||||
" title_family = :title_family, "
|
||||
" title_size = :title_size, "
|
||||
" title_color = :title_color, "
|
||||
" content_family = :content_family, "
|
||||
" content_size = :content_size, "
|
||||
" content_color = :content_color, "
|
||||
" duration = :duration "
|
||||
"WHERE port = :old_port"
|
||||
);
|
||||
|
||||
query.bindValue(":name", name);
|
||||
query.bindValue(":port", server->port());
|
||||
query.bindValue(":block_color", server->getBlockColor());
|
||||
query.bindValue(":border_color", server->getBorderColor());
|
||||
query.bindValue(":border_size", server->getBorderSize());
|
||||
query.bindValue(":transparency", server->getTransparency());
|
||||
query.bindValue(":page_background_color", server->getPageBackgroundColor());
|
||||
query.bindValue(":title_family", server->getTitleFamily());
|
||||
query.bindValue(":title_size", server->getTitleSize());
|
||||
query.bindValue(":title_color", server->getTitleColor());
|
||||
query.bindValue(":content_family", server->getContentFamily());
|
||||
query.bindValue(":content_size", server->getContentSize());
|
||||
query.bindValue(":content_color", server->getContentColor());
|
||||
query.bindValue(":duration", server->getDuration());
|
||||
query.bindValue(":old_port", oldPort);
|
||||
|
||||
if (!query.exec()) {
|
||||
m_lastError = query.lastError().text();
|
||||
qWarning() << "Failed to update notification:" << m_lastError;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QList<NotificationSettings> uDataBase::loadAllNotifications()
|
||||
{
|
||||
QList<NotificationSettings> notifications;
|
||||
|
||||
if (!m_db.isOpen()) {
|
||||
m_lastError = "Database is not open";
|
||||
return notifications;
|
||||
}
|
||||
|
||||
if (!tableExists("notifications")) {
|
||||
// Если таблицы нет, ничего не загружаем
|
||||
return notifications;
|
||||
}
|
||||
|
||||
QSqlQuery query(m_db);
|
||||
QString sql = "SELECT * FROM notifications ORDER BY created_at";
|
||||
|
||||
if (!query.exec(sql)) {
|
||||
m_lastError = query.lastError().text();
|
||||
qWarning() << "Failed to load notifications:" << m_lastError;
|
||||
return notifications;
|
||||
}
|
||||
|
||||
while (query.next()) {
|
||||
NotificationSettings settings;
|
||||
settings.id = query.value("id").toInt();
|
||||
settings.name = query.value("name").toString();
|
||||
settings.type = query.value("type").toString();
|
||||
settings.port = query.value("port").toInt();
|
||||
settings.blockColor = query.value("block_color").toString();
|
||||
settings.borderColor = query.value("border_color").toString();
|
||||
settings.borderSize = query.value("border_size").toInt();
|
||||
settings.transparency = query.value("transparency").toInt();
|
||||
settings.pageBackgroundColor = query.value("page_background_color").toString();
|
||||
settings.titleFamily = query.value("title_family").toString();
|
||||
settings.titleSize = query.value("title_size").toInt();
|
||||
settings.titleColor = query.value("title_color").toString();
|
||||
settings.contentFamily = query.value("content_family").toString();
|
||||
settings.contentSize = query.value("content_size").toInt();
|
||||
settings.contentColor = query.value("content_color").toString();
|
||||
settings.duration = query.value("duration").toInt();
|
||||
settings.createdAt = query.value("created_at").toDateTime();
|
||||
|
||||
notifications.append(settings);
|
||||
}
|
||||
|
||||
return notifications;
|
||||
}
|
||||
|
||||
bool uDataBase::deleteNotification(int port)
|
||||
{
|
||||
if (!m_db.isOpen()) {
|
||||
m_lastError = "Database is not open";
|
||||
return false;
|
||||
}
|
||||
|
||||
QSqlQuery query(m_db);
|
||||
query.prepare("DELETE FROM notifications WHERE port = :port");
|
||||
query.bindValue(":port", port);
|
||||
|
||||
if (!query.exec()) {
|
||||
m_lastError = query.lastError().text();
|
||||
qWarning() << "Failed to delete notification:" << m_lastError;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
+29
@@ -13,6 +13,7 @@
|
||||
#include <QTableWidget>
|
||||
#include "timerinfo.h"
|
||||
#include "webserverchat.h"
|
||||
#include "webservernotify.h"
|
||||
|
||||
struct ChatSettings {
|
||||
int id;
|
||||
@@ -35,6 +36,28 @@ struct ChatSettings {
|
||||
bool deleteByTime;
|
||||
};
|
||||
|
||||
struct NotificationSettings {
|
||||
int id;
|
||||
QString name;
|
||||
QString type;
|
||||
int port;
|
||||
QString blockColor;
|
||||
QString borderColor;
|
||||
int borderSize;
|
||||
int transparency;
|
||||
QString pageBackgroundColor;
|
||||
QString titleFamily;
|
||||
int titleSize;
|
||||
QString titleColor;
|
||||
QString contentFamily;
|
||||
int contentSize;
|
||||
QString contentColor;
|
||||
int duration;
|
||||
QDateTime createdAt;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class uDataBase : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -84,6 +107,12 @@ public:
|
||||
bool deleteChat(int port);
|
||||
QList<ChatSettings> loadAllChats();
|
||||
|
||||
// Методы для уведомлений
|
||||
bool createNotificationsTable();
|
||||
bool saveNotification(const QString &name, HttpServer *server);
|
||||
bool updateNotification(const QString &name, HttpServer *server, int oldPort);
|
||||
QList<NotificationSettings> loadAllNotifications();
|
||||
bool deleteNotification(int port);
|
||||
|
||||
// Проверка подключения к БД
|
||||
bool isConnected() const;
|
||||
|
||||
+611
-1052
File diff suppressed because it is too large
Load Diff
+30
-28
@@ -9,8 +9,10 @@
|
||||
#include <ulink.h>
|
||||
#include <udatabase.h>
|
||||
#include <soundmanager.h>
|
||||
#include "commandprocessor.h"
|
||||
#include "fcreatechat.h"
|
||||
#include "fcreatenotify.h"
|
||||
#include "logmanager.h"
|
||||
#include "neuralnetworkmanager.h"
|
||||
#include "ttw_api.h"
|
||||
#include "user_manager.h"
|
||||
@@ -71,17 +73,6 @@ public:
|
||||
RListCommands(const QString &r1, const QString &r2) : R1(r1), R2(r2) {}
|
||||
};
|
||||
|
||||
// Запись лога
|
||||
struct LogEntry {
|
||||
QDateTime time;
|
||||
QString module;
|
||||
QString method;
|
||||
QString message;
|
||||
int code;
|
||||
QString msgType;
|
||||
QColor color;
|
||||
};
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// ПУБЛИЧНЫЕ ЧЛЕНЫ КЛАССА
|
||||
@@ -95,7 +86,7 @@ public:
|
||||
TTwAPI *twitchAPI; // API для работы с Twitch
|
||||
|
||||
// Методы логирования и работы с командами
|
||||
void toLog(QString aModule, QString aMethod, QString aMessage, int aCode);
|
||||
|
||||
void toCommands(QString command, QString response);
|
||||
|
||||
private slots:
|
||||
@@ -128,7 +119,6 @@ private slots:
|
||||
void loadSettings(); // Загрузка настроек
|
||||
void on_btnImportSettings_clicked(); // Импорт настроек
|
||||
void on_btnExportSettings_clicked(); // Экспорт настроек
|
||||
void on_btnSaveSettings_clicked(); // Сохранение настроек
|
||||
|
||||
// ========================================================================
|
||||
// СЛОТЫ ДЛЯ РАБОТЫ С УВЕДОМЛЕНИЯМИ
|
||||
@@ -159,12 +149,7 @@ private slots:
|
||||
// СЛОТЫ ДЛЯ РАБОТЫ С КОМАНДАМИ И ОТВЕТАМИ
|
||||
// ========================================================================
|
||||
void execCommand(const QString &sender, const QString &message);
|
||||
QString ResponsParserStatic(const QString &inMess, const QString &adName, const QString &aCommandText);
|
||||
QString ResponsParserRandoms(const QString &inMess);
|
||||
QString ResponsParserSounds(const QString &inMess);
|
||||
QString ResponsParserText(const QString &inMess);
|
||||
QString ResponsParserAI(const QString &response, const QString &pall);
|
||||
QString responsParserAPI(const QString &inMess, const QString &adName);
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// СЛОТЫ ДЛЯ РАБОТЫ С ИСКУССТВЕННЫМ ИНТЕЛЛЕКТОМ
|
||||
@@ -199,9 +184,13 @@ private slots:
|
||||
// ========================================================================
|
||||
// СЛОТЫ ДЛЯ РАБОТЫ С ЛОГАМИ
|
||||
// ========================================================================
|
||||
void applyLogFilter();
|
||||
void onLogEntryAdded(const LogEntry& entry);
|
||||
void onLogCleared();
|
||||
|
||||
// Добавляем методы для фильтрации
|
||||
void addLogToTable(const LogEntry &entry, int row);
|
||||
bool shouldShowLogEntry(int code);
|
||||
void applyLogFilter();
|
||||
bool shouldShowLogEntry(LogLevel level); // Изменяем тип параметра
|
||||
|
||||
// ========================================================================
|
||||
// СЛОТЫ ДЛЯ РАБОТЫ С ТАБЛИЦАМИ И ГРИДАМИ
|
||||
@@ -305,8 +294,6 @@ private slots:
|
||||
|
||||
void on_edtDACode_editingFinished();
|
||||
|
||||
void on_cbDAAutoLogin_checkStateChanged(const Qt::CheckState &arg1);
|
||||
|
||||
void on_cbDAAutoLogin_stateChanged(int arg1);
|
||||
|
||||
void on_edtGPTPrefix_editingFinished();
|
||||
@@ -359,6 +346,7 @@ private slots:
|
||||
|
||||
void on_btnThemesFolder_clicked();
|
||||
|
||||
void onNotifyServerUpdated(HttpServer *server, const QString &name);
|
||||
public slots:
|
||||
// Установка статуса подключения к Twitch
|
||||
void setTwitchConnected(bool connected);
|
||||
@@ -372,15 +360,17 @@ private:
|
||||
TAuth *m_authBot; // Авторизация бота
|
||||
TAuth *m_authStreamer; // Авторизация стримера
|
||||
TAuth *m_authDA; // Авторизация DA
|
||||
|
||||
RandomResponses *m_randomResponses;
|
||||
uLink *fLinkForm; // Форма ссылок
|
||||
TTTVAuth *TTVAuth; // Данные авторизации Twitch
|
||||
UserManager *m_userManager; // Менеджер пользователей
|
||||
QList<LogEntry> allLogs; // Все записи логов
|
||||
|
||||
CommandProcessor* m_commandProcessor; // Процессор команд
|
||||
WebSocketClient *m_twitchClient; // WebSocket клиент для Twitch
|
||||
UserWidget* m_userWidget; // Виджет пользователя
|
||||
NeuralNetworkManager *nnManager; // Менеджер нейронных сетей
|
||||
RandomManager *m_randomManager;
|
||||
MediaFileManager *m_SoundFiles;
|
||||
MediaFileManager *m_TextFiles;
|
||||
|
||||
QList<TimerInfo> m_timers; // Список таймеров
|
||||
int m_nextTimerId = 1; // Следующий ID таймера
|
||||
@@ -430,7 +420,7 @@ private:
|
||||
// Метод для применения QSS темы
|
||||
void applyStyleSheet(const QString &filename);
|
||||
|
||||
QString getUserFilePath(const QString &type, const QString &fileName) const;
|
||||
|
||||
QString getUserDataPath() const;
|
||||
QString getSystemPath() const;
|
||||
void setupButtonIcons();
|
||||
@@ -455,7 +445,10 @@ private:
|
||||
// Воспроизведение уведомлений
|
||||
void playNotify(const bool &isMod, const bool &isVip, const bool &isSub);
|
||||
|
||||
|
||||
void loadCommandsFromTableWidget();
|
||||
void loadRandomRangesFromTableWidget();
|
||||
void loadRandomResponseGroupsFromDatabase();
|
||||
void loadSoundsFromDatabase();
|
||||
// Инициализация базы данных
|
||||
void initializeDatabase();
|
||||
// Настройка пользовательского интерфейса
|
||||
@@ -476,10 +469,19 @@ private:
|
||||
void loadEmoties();
|
||||
void loadChatBadges();
|
||||
|
||||
/**
|
||||
* @brief Инициализирует звуковые уведомления
|
||||
*/
|
||||
void initializeNotificationSounds();
|
||||
|
||||
// Обработка эмодзи
|
||||
void processEmotes(QString &message);
|
||||
|
||||
void loadSavedChats();
|
||||
void loadSavedNotifications();
|
||||
|
||||
void processUserCommand(const QString &username, const QString &commandText);
|
||||
void sendChatResponse(const QString &response);
|
||||
};
|
||||
|
||||
#endif // UGENERAL_H
|
||||
|
||||
+21
-4
@@ -6,6 +6,7 @@
|
||||
#include <QJsonObject>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QRandomGenerator>
|
||||
|
||||
UserManager::UserManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
@@ -295,16 +296,32 @@ QVector<User*> UserManager::findUsersByLogin(const QString &login)
|
||||
return result;
|
||||
}
|
||||
|
||||
User* UserManager::getUserByIndex(int index)
|
||||
User* UserManager::getUserByIndex(const QString& userID)
|
||||
{
|
||||
if (index >= 0 && index < m_users.size()) {
|
||||
auto it = m_users.begin();
|
||||
std::advance(it, index);
|
||||
auto it = m_users.find(userID);
|
||||
if (it != m_users.end()) {
|
||||
return &it.value();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const User* UserManager::getRandomUser() const {
|
||||
if (m_users.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QList<QString> keys = m_users.keys();
|
||||
int randomIndex = QRandomGenerator::global()->bounded(keys.size());
|
||||
|
||||
// Возвращаем указатель на существующий объект в контейнере
|
||||
const auto it = m_users.constFind(keys[randomIndex]);
|
||||
if (it != m_users.constEnd()) {
|
||||
return &(it.value()); // Указатель на существующий объект
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const QMap<QString, User>& UserManager::getAllUsers() const
|
||||
{
|
||||
return m_users;
|
||||
|
||||
+2
-2
@@ -38,7 +38,7 @@ public:
|
||||
QVector<User*> getVIPs() const;
|
||||
QVector<User*> getSubscribers() const;
|
||||
QVector<User*> getActiveUsers(int minutes = 10) const;
|
||||
|
||||
const User* getRandomUser() const;
|
||||
// Статистика
|
||||
int getUserCount() const;
|
||||
int getMessageCount() const;
|
||||
@@ -48,7 +48,7 @@ public:
|
||||
bool loadFromFile(const QString &filename);
|
||||
|
||||
// Геттеры
|
||||
User* getUserByIndex(int index);
|
||||
User* getUserByIndex(const QString &userID);
|
||||
const QMap<QString, User>& getAllUsers() const;
|
||||
|
||||
signals:
|
||||
|
||||
+66
-6
@@ -10,6 +10,17 @@ HttpServer::HttpServer(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_server(nullptr)
|
||||
, m_pageBackgroundColor("transparent")
|
||||
, m_blockColor("#FFFFFF")
|
||||
, m_borderColor("#000000")
|
||||
, m_borderSize(2)
|
||||
, m_transparency(0)
|
||||
, m_titleFamily("Arial")
|
||||
, m_titleSize(20)
|
||||
, m_titleColor("#FFFFFF")
|
||||
, m_contentFamily("Arial")
|
||||
, m_contentSize(16)
|
||||
, m_contentColor("#F5F5F5")
|
||||
, m_duration(10)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -78,8 +89,6 @@ void HttpServer::addNotification(const Notification ¬ification)
|
||||
|
||||
m_notifications.append(notif);
|
||||
|
||||
|
||||
|
||||
// Обновляем всех подключенных клиентов
|
||||
for (auto client : m_clients) {
|
||||
if (client->state() == QAbstractSocket::ConnectedState) {
|
||||
@@ -236,7 +245,7 @@ QString HttpServer::generateHTML()
|
||||
"<meta charset='UTF-8'>\n"
|
||||
"<title>Web Notifications</title>\n"
|
||||
"<style>\n"
|
||||
"body { background: %1; margin: 0; padding: 0; }\n" // Используем цвет фона страницы
|
||||
"body { background: %1; margin: 0; padding: 0; }\n" // Используем цвет фона страницы
|
||||
"#messages { position: fixed; top: 20px; right: 20px; width: 400px; }\n"
|
||||
".notification { \n"
|
||||
" margin: 10px 0; \n"
|
||||
@@ -257,9 +266,9 @@ QString HttpServer::generateHTML()
|
||||
" from { opacity: 0; transform: translateY(-20px); }\n"
|
||||
" to { opacity: 1; transform: translateY(0); }\n"
|
||||
"}\n"
|
||||
"</style>\n"
|
||||
"</head>\n"
|
||||
"<body>\n"
|
||||
"</style>\n"
|
||||
"</head>\n"
|
||||
"<body>\n"
|
||||
"<div id='messages'></div>\n"
|
||||
"<script>\n"
|
||||
"let notifications = new Map(); // Храним активные уведомления по timestamp\n"
|
||||
@@ -505,3 +514,54 @@ void HttpServer::discardClient()
|
||||
socket->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void HttpServer::setBlockColor(const QString &color) { m_blockColor = color; }
|
||||
QString HttpServer::getBlockColor() const { return m_blockColor; }
|
||||
|
||||
void HttpServer::setBorderColor(const QString &color) { m_borderColor = color; }
|
||||
QString HttpServer::getBorderColor() const { return m_borderColor; }
|
||||
|
||||
void HttpServer::setBorderSize(int size) { m_borderSize = size; }
|
||||
int HttpServer::getBorderSize() const { return m_borderSize; }
|
||||
|
||||
void HttpServer::setTransparency(int transparency) { m_transparency = transparency; }
|
||||
int HttpServer::getTransparency() const { return m_transparency; }
|
||||
|
||||
void HttpServer::setPageBackgroundColor(const QString &color) { m_pageBackgroundColor = color; }
|
||||
QString HttpServer::getPageBackgroundColor() const { return m_pageBackgroundColor; }
|
||||
|
||||
QString HttpServer::getTitleFamily() const { return m_titleFamily; }
|
||||
int HttpServer::getTitleSize() const { return m_titleSize; }
|
||||
QString HttpServer::getTitleColor() const { return m_titleColor; }
|
||||
QString HttpServer::getContentFamily() const { return m_contentFamily; }
|
||||
int HttpServer::getContentSize() const { return m_contentSize; }
|
||||
QString HttpServer::getContentColor() const { return m_contentColor; }
|
||||
|
||||
void HttpServer::setTitleFont(const QString &family, int size, const QString &color) {
|
||||
m_titleFamily = family;
|
||||
m_titleSize = size;
|
||||
m_titleColor = color;
|
||||
}
|
||||
|
||||
void HttpServer::getTitleFont(QString &family, int &size, QString &color) const {
|
||||
family = m_titleFamily;
|
||||
size = m_titleSize;
|
||||
color = m_titleColor;
|
||||
}
|
||||
|
||||
void HttpServer::setContentFont(const QString &family, int size, const QString &color) {
|
||||
m_contentFamily = family;
|
||||
m_contentSize = size;
|
||||
m_contentColor = color;
|
||||
}
|
||||
|
||||
void HttpServer::getContentFont(QString &family, int &size, QString &color) const {
|
||||
family = m_contentFamily;
|
||||
size = m_contentSize;
|
||||
color = m_contentColor;
|
||||
}
|
||||
|
||||
void HttpServer::setDuration(int duration) { m_duration = duration; }
|
||||
int HttpServer::getDuration() const { return m_duration; }
|
||||
|
||||
|
||||
@@ -57,6 +57,33 @@ public:
|
||||
void addNotification(const Notification ¬ification);
|
||||
void clearNotifications();
|
||||
|
||||
// Методы для доступа к настройкам (нужно реализовать хранение)
|
||||
void setBlockColor(const QString &color);
|
||||
QString getBlockColor() const;
|
||||
void setBorderColor(const QString &color);
|
||||
QString getBorderColor() const;
|
||||
void setBorderSize(int size);
|
||||
int getBorderSize() const;
|
||||
void setTransparency(int transparency);
|
||||
int getTransparency() const;
|
||||
void setPageBackgroundColor(const QString &color);
|
||||
QString getPageBackgroundColor() const;
|
||||
void setTitleFont(const QString &family, int size, const QString &color);
|
||||
void getTitleFont(QString &family, int &size, QString &color) const;
|
||||
void setContentFont(const QString &family, int size, const QString &color);
|
||||
void getContentFont(QString &family, int &size, QString &color) const;
|
||||
void setDuration(int duration);
|
||||
int getDuration() const;
|
||||
|
||||
|
||||
QString getTitleFamily() const;
|
||||
int getTitleSize() const;
|
||||
QString getTitleColor() const;
|
||||
QString getContentFamily() const;
|
||||
int getContentSize() const;
|
||||
QString getContentColor() const;
|
||||
|
||||
|
||||
signals:
|
||||
void serverStarted(bool success);
|
||||
|
||||
@@ -71,6 +98,19 @@ private:
|
||||
QList<Notification> m_notifications;
|
||||
QString m_pageBackgroundColor; // Цвет фона страницы
|
||||
|
||||
|
||||
QString m_blockColor;
|
||||
QString m_borderColor;
|
||||
int m_borderSize;
|
||||
int m_transparency;
|
||||
QString m_titleFamily;
|
||||
int m_titleSize;
|
||||
QString m_titleColor;
|
||||
QString m_contentFamily;
|
||||
int m_contentSize;
|
||||
QString m_contentColor;
|
||||
int m_duration;
|
||||
|
||||
void processRequest(QTcpSocket *socket, const QString &request);
|
||||
void sendResponse(QTcpSocket *socket, const QString &contentType, const QString &content, int statusCode = 200);
|
||||
void sendHtmlPage(QTcpSocket *socket);
|
||||
|
||||
Reference in New Issue
Block a user