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