/*
* Copyright 2014-2016 Markus Prasser
*
* This file is part of Labcontrol.
*
* Labcontrol is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Labcontrol is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Labcontrol. If not, see .
*/
#include
#include
#include
#include "lablib.h"
lc::Lablib::Lablib( QPlainTextEdit *argDebugMessagesTextEdit, QObject *argParent ) :
QObject{ argParent },
clientIPsToClientsMap{ new QMap< QString, Client* > },
debugMessagesTextEdit{ argDebugMessagesTextEdit },
labSettings{ "Economic Laboratory", "Labcontrol", this },
occupiedPorts{ new QVector< int > },
sessionsModel{ new SessionsModel{ this } }
{
ReadSettings();
DetectInstalledZTreeVersionsAndLaTeXHeaders();
// Initialize all 'netstat' query mechanisms
if ( !settings->netstatCmd.isEmpty() ) {
netstatAgent = new NetstatAgent{ settings->netstatCmd };
netstatAgent->moveToThread( &netstatThread );
connect( &netstatThread, &QThread::finished, netstatAgent, &QObject::deleteLater );
connect( netstatAgent, &NetstatAgent::QueryFinished,
this, &Lablib::GotNetstatQueryResult );
netstatThread.start();
netstatTimer = new QTimer{ this };
connect( netstatTimer, &QTimer::timeout,
netstatAgent, &NetstatAgent::QueryClientConnections );
netstatTimer->start( 500 );
}
// Initialize the server for client help requests retrieval
if ( clientHelpNotificationServerPort && !settings->serverIP.isEmpty() ) {
clientHelpNotificationServer = new ClientHelpNotificationServer{ clientIPsToClientsMap,
settings->serverIP,
clientHelpNotificationServerPort,
this };
}
}
lc::Lablib::~Lablib () {
if ( netstatTimer ) {
netstatTimer->stop();
delete netstatTimer;
}
netstatThread.quit();
netstatThread.wait();
if ( clients ) {
for ( QVector< Client* >::iterator it = clients->begin(); it != clients->end(); ++it ) {
delete *it;
}
}
delete clients;
delete InstalledZTreeVersions;
delete occupiedPorts;
delete webcams;
}
bool lc::Lablib::CheckIfUserIsAdmin() const {
for ( const auto &s : adminUsers ) {
if ( s == settings->localUserName ) {
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] User '%1' has administrative"
" rights." )
.arg( settings->localUserName ) );
return true;
}
}
return false;
}
void lc::Lablib::DetectInstalledZTreeVersionsAndLaTeXHeaders() {
// Detect the installed LaTeX headers
if ( !settings->lcInstDir.isEmpty() ) {
QDir laTeXDirectory{ settings->lcInstDir, "*header.tex", QDir::Name,
QDir::CaseSensitive | QDir::Files | QDir::Readable };
if ( !laTeXDirectory.exists() || laTeXDirectory.entryList().isEmpty() ) {
QMessageBox messageBox{ QMessageBox::Critical, tr( "No LaTeX headers found" ),
tr( "No LaTeX headers could be found in '%1'. Receipts printing will not work" )
.arg( settings->lcInstDir ), QMessageBox::Ok };
messageBox.exec();
installedLaTeXHeaders = new QStringList{ "None found" };
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] No LaTeX headers could be found in '%1'." )
.arg( settings->lcInstDir ) );
} else {
installedLaTeXHeaders = new QStringList{ laTeXDirectory.entryList() };
installedLaTeXHeaders->replaceInStrings( "_header.tex", "" );
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] LaTeX headers: %1" ).arg( installedLaTeXHeaders->join( " / " ) ) );
}
}
// Detect the installed zTree versions
if ( !settings->zTreeInstDir.isEmpty() ) {
QDir zTreeDirectory{ settings->zTreeInstDir, "zTree*", QDir::Name,
QDir::NoDotAndDotDot | QDir::Dirs
| QDir::Readable | QDir::CaseSensitive };
if ( zTreeDirectory.entryList().isEmpty() ) {
QMessageBox messageBox{ QMessageBox::Critical, tr( "zTree not found" ),
tr( "No zTree installation found in '%1'. Running zTree will not be possible." )
.arg( settings->zTreeInstDir ), QMessageBox::Ok };
messageBox.exec();
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] No zTree versions could be found in '%1'." )
.arg( settings->zTreeInstDir ) );
}
else {
InstalledZTreeVersions = new QStringList{ zTreeDirectory.entryList() };
InstalledZTreeVersions->replaceInStrings( "zTree_", "" );
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] zTree versions: %1" ).arg( InstalledZTreeVersions->join( " / " ) ) ) ;
}
}
}
void lc::Lablib::GotNetstatQueryResult( QStringList *argActiveZLeafConnections ) {
if ( argActiveZLeafConnections != nullptr ) {
for ( auto s: *argActiveZLeafConnections ) {
// Set all given clients' statuses to 'ZLEAF_RUNNING'
emit ZLEAF_RUNNING( s );
}
}
else
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] Netstat status query failed." ) );
delete argActiveZLeafConnections;
}
void lc::Lablib::ReadSettings() {
// Let the local zLeaf name default to 'local' if none was given in the settings
if ( settings->GetLocalzLeafName().isEmpty() ) {
settings->SetLocalzLeafName( tr( "local" ) );
}
// Read the list of users with administrative rights
if ( !labSettings.contains( "admin_users" ) ) {
QMessageBox messageBox{ QMessageBox::Information, tr( "'admin_users' not set" ),
tr( "The 'admin_users' variable was not set. No users will be able to conduct administrative tasks." ) };
messageBox.exec();
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] 'admin_users' was not set. No permission for administrative tasks." ) );
} else {
adminUsers = labSettings.value( "admin_users", "" ).toString()
.split( '|', QString::SkipEmptyParts, Qt::CaseInsensitive );
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] 'admin_users': %1").arg( adminUsers.join(" / ") ) );
}
// Read the port the ClientHelpNotificationServer shall listen on
clientHelpNotificationServerPort = labSettings.value( "client_help_server_port", 0 ).toUInt();
if ( !clientHelpNotificationServerPort ) {
QMessageBox messageBox{ QMessageBox::Information, tr( "The ClientHelpNotificationServer will be deactivated" ),
tr( "The 'client_help_server_port' variable was not set or set to zero. The ClientHelpNotificationServer will be deactivated. Clients' help requests will be ignored by the server." ) };
messageBox.exec();
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] The ClientHelpNotificationServer will be deactivated since 'client_help_server_port' was not set or set to zero." ) );
} else {
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] 'client_help_server_port': %1" ).arg( QString::number( clientHelpNotificationServerPort ) ) );
}
// Read the default receipt index for the 'CBReceipts' combobox
if ( !labSettings.contains( "default_receipt_index" ) ) {
QMessageBox messageBox(QMessageBox::Information, tr( "'default_receipt_index' not set" ), tr( "The 'default_receipt_index' variable was not set. It will default to '0'." ) );
messageBox.exec();
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] 'default_receipt_index' was not set. It will default to '0'." ) );
}
defaultReceiptIndex = labSettings.value( "default_receipt_index", 0 ).toInt();
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] 'default_receipt_index': %1").arg( QString::number( defaultReceiptIndex ) ) );
// Read the initial port number
if ( !labSettings.contains( "initial_port" ) ) {
QMessageBox messageBox{ QMessageBox::Information, tr( "'initial_port' not set" ),
tr( "The 'initial_port' variable was not set. Labcontrol will default to port 7000 for new zTree instances." ) };
messageBox.exec();
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] 'initial_port' was not set. Labcontrol will default to port 7000 for new zTree instances." ) );
}
chosenZTreePort = labSettings.value( "initial_port", 7000 ).toInt();
debugMessagesTextEdit->appendPlainText( tr("[DEBUG] 'initial_port': %1" ).arg( QString::number( chosenZTreePort ) ) );
// Get a list of available webcams in the lab
if ( !labSettings.contains( "webcams" ) ) {
QMessageBox messageBox{ QMessageBox::Information, tr( "'webcams' not set" ), tr( "The 'webcams' variable was not set. No stationary webcams will be available." ) };
messageBox.exec();
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] 'webcams' was not set. No stationary webcams will be available." ) );
}
else {
webcams = new QStringList{ labSettings.value( "webcams" ).toString().split( '|', QString::SkipEmptyParts, Qt::CaseInsensitive ) };
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] 'webcams': %1" ).arg( webcams->join( " / " ) ) );
}
// Get the client quantity to check the value lists for clients creation for correct length
int clientQuantity = 0;
if ( !labSettings.contains("client_quantity" ) ) {
QMessageBox messageBox{ QMessageBox::Information, tr( "'client_quantity' not set" ),
tr( "The 'client_quantity' variable was not set. The client quantity will be guessed by the amount of client ips set in 'client_ips'." ) };
messageBox.exec();
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] 'client_quantity' was not set. The client quantity will be guessed by the amount of client IPs set in 'client_ips'." ) );
clientQuantity = labSettings.value( "client_ips", "" ).toString().split( '/', QString::SkipEmptyParts, Qt::CaseSensitive ).length();
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] 'client_quantity': %1" ).arg( QString::number( clientQuantity ) ) );
} else {
clientQuantity = labSettings.value( "client_quantity" ).toInt();
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] 'client_quantity': %1" ).arg( QString::number( clientQuantity ) ) );
}
// Create all the clients in the lab
QStringList clientIPs = labSettings.value( "client_ips" ).toString().split( '|', QString::SkipEmptyParts, Qt::CaseSensitive );
if ( clientIPs.length() != clientQuantity ) {
QMessageBox messageBox{ QMessageBox::Critical, tr( "Wrong client ip quantity" ),
tr( "The quantity of client ips does not match the client quantity. Client creation will fail. No clients will be available for interaction." ), QMessageBox::Ok };
messageBox.exec();
return;
}
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] Client IPs: %1").arg( clientIPs.join( " / " ) ) );
QStringList clientMACs = labSettings.value( "client_macs" ).toString().split( '|', QString::SkipEmptyParts, Qt::CaseSensitive );
if ( clientMACs.length() != clientQuantity ) {
QMessageBox messageBox{ QMessageBox::Critical, tr( "Wrong client mac quantity" ),
tr( "The quantity of client macs does not match the client quantity. Client creation will fail. No clients will be available for interaction." ), QMessageBox::Ok };
messageBox.exec();
return;
}
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] Client MACs: %1").arg( clientMACs.join( " / " ) ) );
QStringList clientNames = labSettings.value( "client_names" ).toString().split( '|', QString::SkipEmptyParts, Qt::CaseSensitive );
if ( clientNames.length() != clientQuantity ) {
QMessageBox messageBox{ QMessageBox::Critical, tr( "Wrong client name quantity" ),
tr( "The quantity of client names does not match the client quantity. Client creation will fail. No clients will be available for interaction." ), QMessageBox::Ok };
messageBox.exec();
return;
}
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] Client names: %1" ).arg( clientNames.join( " / " ) ) );
QStringList clientXPositions = labSettings.value( "client_xpos" ).toString().split( '|', QString::SkipEmptyParts, Qt::CaseSensitive );
if ( clientXPositions.length() != clientQuantity ) {
QMessageBox messageBox{ QMessageBox::Critical, tr( "Wrong client x positions quantity" ),
tr( "The quantity of client x positions does not match the client quantity. Client creation will fail. No clients will be available for interaction." ), QMessageBox::Ok };
messageBox.exec();
return;
}
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] client_xpos: %1" ).arg( clientXPositions.join( " / " ) ) );
QStringList clientYPositions = labSettings.value( "client_ypos" ).toString().split( '|', QString::SkipEmptyParts, Qt::CaseSensitive );
if ( clientYPositions.length() != clientQuantity ) {
QMessageBox messageBox{ QMessageBox::Critical, tr( "Wrong client y positions quantity" ),
tr( "The quantity of client y positions does not match the client quantity. Client creation will fail. No clients will be available for interaction." ), QMessageBox::Ok };
messageBox.exec();
return;
}
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] client_ypos: %1" ).arg( clientYPositions.join( " / " ) ) );
clients = new QVector< Client* >;
for ( int i = 0; i < clientQuantity; i++ ) {
clients->append( new Client{ debugMessagesTextEdit, &clientIPs[ i ], &clientMACs[ i ],
&clientNames[ i ], clientXPositions[ i ].toUShort(),
clientYPositions[ i ].toUShort() } );
// Add an corresponding entry to the 'client_ips_to_clients_map' std::map
( *clientIPsToClientsMap )[ clients->last()->ip ] = clients->last();
// Get the address of the Client instance in RAM for display
const void *clientPointerAddress = static_cast< const void* >( ( *clientIPsToClientsMap )[ clients->last()->ip ] );
QString clientPointerAddressString;
QTextStream clientPointerAddressStream ( &clientPointerAddressString );
clientPointerAddressStream << clientPointerAddress;
// Connect the 'Client' instance to the 'ZLEAF_RUNNING(QString client_ip)' signal
connect( this, &Lablib::ZLEAF_RUNNING,
clients->last(), &Client::SetStateToZLEAF_RUNNING );
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] Added '%1' to 'client_ips_to_clients_map': '%2'" ).arg( clients->last()->name ).arg( clientPointerAddressString ) );
}
}
void lc::Lablib::SetChosenZTreeDataTargetPath( const QString &argZTreeDataTargetPath ) {
chosenZTreeDataTargetPath = argZTreeDataTargetPath;
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] chosen_zTree_data_target_path set to: '%1'" ).arg( chosenZTreeDataTargetPath ) );
}
void lc::Lablib::SetChosenZTreePort( const int &argPort ) {
chosenZTreePort = argPort;
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] chosen_zTree_port set to: '%1'" ).arg( QString::number( chosenZTreePort ) ) );
}
void lc::Lablib::SetPrintReceiptsForLocalClients( const bool &argPrintReceiptsForLocalClients ) {
PrintReceiptsForLocalClients = argPrintReceiptsForLocalClients;
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] Set print_receipts_for_local_clients to : '%1'" ).arg( QString::number( PrintReceiptsForLocalClients ) ) );
}
void lc::Lablib::ShowOrsee() {
QProcess showOrseeProcess;
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
showOrseeProcess.setProcessEnvironment( env );
QString program{ settings->browserCmd };
QStringList arguments{ QStringList{} << settings->orseeUrl };
showOrseeProcess.startDetached( program, arguments );
// Output message via the debug messages tab
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] %1 %2" ).arg( program ).arg( arguments.join( " " ) ) );
}
void lc::Lablib::ShowPreprints() {
// Start the process
QProcess showPreprintsProcess;
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
showPreprintsProcess.setProcessEnvironment( env );
QString program{ settings->fileMngr };
QStringList arguments{ QStringList{} << settings->lcInstDir + "/preprints" };
showPreprintsProcess.startDetached( program, arguments );
// Output message via the debug messages tab
debugMessagesTextEdit->appendPlainText( tr( "[DEBUG] %1 %2" ).arg( program ).arg( arguments.join( " " ) ) );
}
void lc::Lablib::StartNewZTreeInstance( QString argDataTargetPath, int argPort,
QString argzTreeVersion,
bool argReceiptsForLocalClients,
QString argAnonReceiptPlaceholder,
QString argChosenLatexHeader ) {
if ( !QDir( argDataTargetPath ).exists() ) {
QMessageBox messageBox{ QMessageBox::Critical, tr( "Data target path does not exist" ),
tr( "Your chosen data target path does not exist."
" Do you want it to be created automatically?" ),
QMessageBox::Yes | QMessageBox::No };
messageBox.exec();
if ( messageBox.clickedButton() == messageBox.button( QMessageBox::No ) ) {
QMessageBox messageBox{ QMessageBox::Critical, tr( "Data target directory will not"
" be created" ),
tr( "Your chosen data target directory does not exist and"
" will not be created. Please choose another one." ),
QMessageBox::Ok };
messageBox.exec();
return;
} else {
if ( !QDir().mkpath( argDataTargetPath ) ) {
QMessageBox messageBox{ QMessageBox::Critical, tr( "Data target directory could"
" not be created" ),
tr( "Your chosen data target directory does not exist"
" and could not be created. Please choose another"
" one." ), QMessageBox::Ok };
messageBox.exec();
return;
}
}
}
try {
sessionsModel->push_back( new Session{ debugMessagesTextEdit, argDataTargetPath,
argPort, argzTreeVersion,
argReceiptsForLocalClients,
argAnonReceiptPlaceholder,
argChosenLatexHeader } );
occupiedPorts->append( sessionsModel->back()->zTreePort );
}
catch ( Session::lcDataTargetPathCreationFailed ) {
QMessageBox::information( nullptr, tr( "Chosen data target path could not be created" ),
tr( "The path specified by your chosen data target path '%1'"
" could not be created. Please check if it is a valid"
" location and you have all needed permissions." )
.arg( argDataTargetPath ) );
}
}
void lc::Lablib::SetLocalZLeafDefaultName( const QString &argName ) {
settings->SetLocalzLeafName( argName );
labSettings.setValue( "local_zLeaf_name", argName );
}