Розглянемо тепер сервер та клієнта.
З питаннями, що стосуються спільної організації взаємодії клієнта та сервера, можна ознайомитись у цій статті: " Приклад використання QLocalServer та QLocalSocket ". Тут я торкнуся лише моментів, безпосередньо пов'язаних із передачею інформації щодо завдання та закриттям завдання.
Сервер
// trackerserver.cpp TrackerServer::TrackerServer(QWidget* parent) : QWidget(parent) { // Задаём имя сервера nameServer = "TrackerServer"; // Устанавливаем размер следующего блока равным нулю nextBlockSize = 0; ... } // Метод создания и запуска сервера void TrackerServer::createServer() { localServer = new QLocalServer(this); // Если сервер не запустить, выдать сообщение и завершить программу if(!localServer->listen(nameServer)) { QMessageBox::critical(0, tr("Ошибка сервера"), tr("Невозможно запустить сервер ") + nameServer + ": " + localServer->errorString()); localServer->close(); return; } } ... // Метод создания подключений void TrackerServer::createConnections() { // Подключение сигнала сервера о новом подключении к обработчику нового подключения connect(localServer, &QLocalServer::newConnection, this, &TrackerServer::slotNewConnection); ... }
Тепер розглянемо слоти сервера.
// trackerserver.cpp // Слот обработки нового соединения void TrackerServer::slotNewConnection() { // Получаем сокет, подключённый к серверу QLocalSocket* socket = localServer->nextPendingConnection(); // Соединяем сигнал отключения сокета с обработчиком удаления сокета connect(socket, &QLocalSocket::disconnected, socket, &QLocalSocket::deleteLater); // Соединяем сигнал сокета о готовности передачи данных со слотом закрытия задачи connect(socket, &QLocalSocket::readyRead, this, &TrackerServer::slotCloseTask); } // Слот получения информации от клиента и закрытия задачи void TrackerServer::slotCloseTask() { QLocalSocket* socket = (QLocalSocket*)sender(); QDataStream in(socket); in.setVersion(QDataStream::Qt_5_3); for(;;) { if(!nextBlockSize) { if(socket->bytesAvailable() < (int)sizeof(quint16)) break; in >> nextBlockSize; } in >> clientTaskNumber >> clientRevision; nextBlockSize = 0; } if(!database->isClosed(clientTaskNumber) && clientTaskNumber && clientRevision) // Если задача не закрыта и если номер задачи и ревизия не пустые, закрываем задачу { database->closeTask(clientTaskNumber, QString("%1").arg(clientRevision)); // Обновляем SQL-таблицу задач taskSqlTableModel->select(); // Если все задачи для текущего проекта закрыты if(database->isClosedAllTasks(projectCurrentId)) // сделать доступной кнопку "Архивировать" archiveProjectButton->setEnabled(true); else // иначе блокировать кнопку "Архивировать" archiveProjectButton->setDisabled(true); } }
клієнт
// main.cpp #include "trackerclient.h" #include <QCoreApplication> #include <QFile> #include <QTextStream> #include <QDebug> #include <QRegExp> int main(int argc, char* argv[]) { QCoreApplication app(argc, argv); // Считываем из аргумента 1 командной строки путь к файлу log.txt QString pathToFile = argv[1]; QString result; // Если файл открывается, считываем данные из файла в строку QFile file(pathToFile); if(file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream stream(&file); result = stream.readAll(); file.close(); } else qDebug() << "Unable open file " + pathToFile; int taskNumber = 0; int revisionNumber = 0; // С помощью регулярного выражения извлекаем из строки номер задачи и номер ревизии QString expr = "(.*)(\\#\\{)(\\d+)(\\})(.*)(r\\{)(\\d+)(\\})"; QRegExp taskRevision(expr); int position = taskRevision.indexIn(result); if(position > -1) { taskNumber = taskRevision.cap(3).toInt(); // это номер задачи revisionNumber = taskRevision.cap(7).toInt(); // это номер ревизии } // Инициализируем клиента с соответствующими параметрами TrackerClient client("TrackerServer", taskNumber, revisionNumber); app.exit(0); }
Тепер розберемо питання, звідки береться log.txt.
Як я вже писав у попередній частині, для спрощення трекера за кнопкою створення проектів можна створити лише проект без підтримки ВКВ. Тобто такий віртуальний проект, який існує лише у базі даних трекера і більше ніде.
Проекти за допомогою ВКВ формуються за допомогою bat-файлу. Його я написав раніше з необхідності створювати і використовувати репозиторії SVN так, щоб вони за наявності коммітів автоматизовано архівувалися. Розглянемо його в тій частині, яка стосується створення проектів за допомогою ВКВ, тобто вже реально існуючих у файловій системі проектів.
rem Запрашиваем наименование проекта set /p repo_name=[Input name project]: echo Name repository: %repo_name% rem Устанавливаем путь к репозиторию set repo_path=%repo_root%\%repo_name% echo Full path to repository: %repo_path% rem Создаём репозиторий svnadmin create %repo_path% rem Очищаем папку репозитория hooks del /f /q %repo_path%\hooks\ rem Создаём / копируем в папке hooks файлы ini.bat, pre-commit.bat, post-commit.bat echo set commit=n>%repo_path%\hooks\ini.bat copy pre-commit.bat %repo_path%\hooks\ copy post-commit2.bat %repo_path%\hooks\post-commit.bat rem Импортируем файловую структуру по умолчанию и формируем первый коммит svn import %def_tree% file:///%repo_path% -m "initial import"
Файл ini.bat містить у собі прапор commit, що встановлюється в "n", якщо коммітів немає, або встановлюється в "y", якщо є хоча б один коміт. За його станом скрипт архівації визначає, чи треба архівувати цей репозиторій.
rem pre-commit.bat @echo off rem Считываем переменные из командной строки set repo_path=%1 set transaction=%2 rem Записываем сообщение коммита в файл log.txt "G:\soft\svn\bin\svnlook.exe" log -t "%transaction%" "%repo_path%">%repo_path%\hooks\log.txt
У повідомленні комміту міститься номер завдання, що закривається у форматі #{ <номер задачі> } .
rem post-commit.bat @echo off rem Считываем переменные из командной строки set repo_path=%1 set revision=%2 rem Устанавливаем в файле ini.bat флаг commit в "y" echo set commit=y>%repo_path%\hooks\ini.bat rem Записываем номер ревизии в файл log.txt echo r{%revision%}>>%repo_path%\hooks\log.txt rem %path_to_tracker%\ICTrackerClient.exe %repo_path%\hooks\log.txt
Після запису ревізії post-commit.bat файл log.txt виглядає приблизно так.
Closing task #{2} end some editing files r{29}
І останнім викликається клієнт, який зчитує ці дані із файлу.
Залишилося лише передати ці дані серверу.
// trackerclient.h #ifndef TRACKERCLIENT_H #define TRACKERCLIENT_H #include <QObject> #include <QLocalSocket> class TrackerClient : public QObject { Q_OBJECT public: explicit TrackerClient(const QString& serverName, int taskId, int revision, QObject* parent = 0); ~TrackerClient(); private: QLocalSocket* localSocket; int taskId; int revision; private slots: void sendToServer(); }; #endif // TRACKERCLIENT_H
// trackerclient.cpp #include "trackerclient.h" #include <QDataStream> TrackerClient::TrackerClient(const QString &serverName, int taskId, int revision, QObject* parent) : QObject(parent), taskId(taskId), revision(revision) { localSocket = new QLocalSocket(this); localSocket->setServerName(serverName); connect(localSocket, &QLocalSocket::connected, this, &TrackerClient::sendToServer); localSocket->connectToServer(); } TrackerClient::~TrackerClient() { } void TrackerClient::sendToServer() { QByteArray arrayBlock; QDataStream out(&arrayBlock, QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_5_3); out << quint16(0) << taskId << revision; out.device()->seek(0); out << quint16(arrayBlock.size() - sizeof(quint16)); localSocket->write(arrayBlock); localSocket->disconnectFromServer(); }
Добрый день, и снова немного ревью.
Вот это каст в стиле Си
Лучше кастовать в стиле С++, мы же на С++ пишем.
Получается несколько многословнее, но честно, в большом проекте хоть есть за что глазу зацепиться. Плюс более контролируемый каст.
Далее, думаю, что вот это
Можно переписать так
setEnabled и setDisabled делают тоже самое.
Вот это
Нужно переписать так
nullptr - это специальный псевдотип данных для нулевого указателя. В случае с 0, там по сути может оказаться какой угодно мусор в зависимости от платформы. В случае с nullptr поведение будет предсказуемым.
Далее вот это объявление
Наверное стоит вам сразу писать как
Это принятый код-стайл в Qt, а также именно на такую запись заточены макросы Q_PROPERTY. Ну и стоит в деструкторе удалять m_localSocket, чтобы не было утчечек памяти. Вообще этот как раз тот случай, где имеет смысл использовать умные указатели, как вы делали в прошлой статье.
Да, я уже со static_cast освоился, такая запись понятнее.
Ага.
Понял.
А что означает буква m перед наименованием? Эти буквы меня всегда путают и с толку сбивают.
member - член класса, некоторое поле. В Qt это обычное обозначение тех объектов, которые объявлены в private или protected секциях.
Просто в Q_PROPERTY это прописывается без m_ но макрос разворачивается на член класса, который содержит этот префикс.
Как правильно объявить умный указатель и инициализировать по отдельности? Я пробовал так:
выдаёт ошибку при компиляции.
Теперь на коннекте ошибку выдаёт:
connect(m_localSocket.get(), &QLocalSocket::connected, this, &TrackerClient::sendToServer);