Рассмотрим теперь сервер и клиента.
С вопросами, касающимися общей организации взаимодействия клиента и сервера, можно ознакомиться в этой статье: " Пример использования 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);