diff --git a/Jamulus.pro b/Jamulus.pro index b517c99b..1421d443 100755 --- a/Jamulus.pro +++ b/Jamulus.pro @@ -210,7 +210,8 @@ HEADERS += src/audiomixerboard.h \ src/recorder/jamrecorder.h \ src/recorder/creaperproject.h \ src/recorder/cwavestream.h \ - src/historygraph.h + src/historygraph.h \ + src/signalhandler.h HEADERS_OPUS = libs/opus/celt/arch.h \ libs/opus/celt/bands.h \ @@ -324,6 +325,7 @@ SOURCES += src/audiomixerboard.cpp \ src/serverlist.cpp \ src/serverlogging.cpp \ src/settings.cpp \ + src/signalhandler.cpp \ src/socket.cpp \ src/soundbase.cpp \ src/util.cpp \ diff --git a/src/client.cpp b/src/client.cpp index b4a60743..1259e3ee 100755 --- a/src/client.cpp +++ b/src/client.cpp @@ -1207,7 +1207,7 @@ fflush(pFileDelay); // update socket buffer size Channel.UpdateSocketBufferSize(); - Q_UNUSED ( iUnused ); + Q_UNUSED ( iUnused ) } int CClient::EstimatedOverallDelay ( const int iPingTimeMs ) diff --git a/src/recorder/jamrecorder.cpp b/src/recorder/jamrecorder.cpp index 1055efdc..2c817919 100755 --- a/src/recorder/jamrecorder.cpp +++ b/src/recorder/jamrecorder.cpp @@ -314,22 +314,31 @@ void CJamRecorder::Init( const CServer* server, throw std::runtime_error( (recordBaseDir.absolutePath() + " is a directory but cannot be written to").toStdString() ); } - QObject::connect((const QObject *)server, SIGNAL ( Stopped() ), - this, SLOT( OnEnd() ), - Qt::ConnectionType::QueuedConnection); + QObject::connect( (const QObject *)server, SIGNAL ( Stopped() ), + this, SLOT( OnEnd() ), + Qt::ConnectionType::QueuedConnection ); - QObject::connect((const QObject *)server, SIGNAL ( ClientDisconnected(int) ), - this, SLOT( OnDisconnected(int) ), - Qt::ConnectionType::QueuedConnection); + QObject::connect( (const QObject *)server, SIGNAL ( ClientDisconnected ( int ) ), + this, SLOT( OnDisconnected ( int ) ), + Qt::ConnectionType::QueuedConnection ); qRegisterMetaType>(); - QObject::connect((const QObject *)server, SIGNAL ( AudioFrame(const int, const QString, const CHostAddress, const int, const CVector) ), - this, SLOT( OnFrame(const int, const QString, const CHostAddress, const int, const CVector) ), - Qt::ConnectionType::QueuedConnection); + QObject::connect( (const QObject *)server, SIGNAL ( AudioFrame( const int, const QString, const CHostAddress, const int, const CVector ) ), + this, SLOT( OnFrame (const int, const QString, const CHostAddress, const int, const CVector ) ), + Qt::ConnectionType::QueuedConnection ); + + QObject::connect( QCoreApplication::instance(), + SIGNAL ( aboutToQuit() ), + this, SLOT( OnAboutToQuit() ) ); iServerFrameSizeSamples = _iServerFrameSizeSamples; + + thisThread = new QThread(); + moveToThread ( thisThread ); + thisThread->start(); } + /** * @brief CJamRecorder::OnStart Start up tasks when the first client connects */ @@ -337,7 +346,7 @@ void CJamRecorder::OnStart() { // Ensure any previous cleaning up has been done. OnEnd(); - currentSession = new CJamSession(recordBaseDir); + currentSession = new CJamSession( recordBaseDir ); isRecording = true; } @@ -346,7 +355,7 @@ void CJamRecorder::OnStart() { */ void CJamRecorder::OnEnd() { - if (isRecording) + if ( isRecording ) { isRecording = false; currentSession->End(); @@ -372,6 +381,13 @@ void CJamRecorder::OnEnd() } } +void CJamRecorder::OnAboutToQuit() +{ + OnEnd(); + + thisThread->exit(); +} + /** * @brief CJamRecorder::SessionDirToReaper Replica of CJamRecorder::OnEnd() but using the directory contents to construct the CReaperProject object * @param strSessionDirName @@ -409,11 +425,11 @@ void CJamRecorder::SessionDirToReaper(QString& strSessionDirName, int serverFram */ void CJamRecorder::OnDisconnected(int iChID) { - if (!isRecording) + if ( !isRecording ) { qWarning() << "CJamRecorder::OnDisconnected: channel" << iChID << "disconnected but not recording"; } - if (currentSession == nullptr) + if ( currentSession == nullptr ) { qWarning() << "CJamRecorder::OnDisconnected: channel" << iChID << "disconnected but no currentSession"; return; @@ -434,7 +450,7 @@ void CJamRecorder::OnDisconnected(int iChID) void CJamRecorder::OnFrame(const int iChID, const QString name, const CHostAddress address, const int numAudioChannels, const CVector data) { // Make sure we are ready - if (!isRecording) + if ( !isRecording ) { OnStart(); } diff --git a/src/recorder/jamrecorder.h b/src/recorder/jamrecorder.h index cc49705d..eb5a0581 100755 --- a/src/recorder/jamrecorder.h +++ b/src/recorder/jamrecorder.h @@ -132,13 +132,16 @@ private: QList jamClientConnections; }; -class CJamRecorder : public QThread +class CJamRecorder : public QObject { Q_OBJECT public: - CJamRecorder(const QString recordingDirName) : - recordBaseDir (recordingDirName), isRecording (false) {} + CJamRecorder ( const QString recordingDirName ) : + recordBaseDir ( recordingDirName ), + isRecording ( false ) + { + } void Init( const CServer* server, const int _iServerFrameSizeSamples ); @@ -155,16 +158,21 @@ public slots: */ void OnEnd(); + /** + * @brief Raised when application is stopping + */ + void OnAboutToQuit(); + /** * @brief Raised when an existing client leaves the server. * @param iChID channel number of client */ - void OnDisconnected(int iChID); + void OnDisconnected ( int iChID ); /** - * @brief Raised when a frame of data fis available to process + * @brief Raised when a frame of data is available to process */ - void OnFrame(const int iChID, const QString name, const CHostAddress address, const int numAudioChannels, const CVector data); + void OnFrame ( const int iChID, const QString name, const CHostAddress address, const int numAudioChannels, const CVector data ); private: QDir recordBaseDir; @@ -172,6 +180,8 @@ private: bool isRecording; CJamSession* currentSession; int iServerFrameSizeSamples; + + QThread* thisThread; }; } diff --git a/src/server.cpp b/src/server.cpp index 7ba26c9f..81612155 100755 --- a/src/server.cpp +++ b/src/server.cpp @@ -398,11 +398,10 @@ CServer::CServer ( const int iNewMaxNumChan, QString().number( static_cast ( iPortNumber ) ) ); } - // Enable jam recording (if requested) + // Enable jam recording (if requested) - kicks off the thread if ( bEnableRecording ) { JamRecorder.Init ( this, iServerFrameSizeSamples ); - JamRecorder.start(); } // enable all channels (for the server all channel must be enabled the @@ -466,6 +465,14 @@ CServer::CServer ( const int iNewMaxNumChan, SIGNAL ( SvrRegStatusChanged() ), this, SLOT ( OnSvrRegStatusChanged() ) ); + QObject::connect ( QCoreApplication::instance(), + SIGNAL ( aboutToQuit() ), + this, SLOT ( OnAboutToQuit() ) ); + + QObject::connect ( pSignalHandler, + SIGNAL ( ShutdownSignal ( int ) ), + this, SLOT ( OnShutdown ( int ) ) ); + #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) connectChannelSignalsToServerSlots(); @@ -908,6 +915,54 @@ void CServer::OnCLDisconnection ( CHostAddress InetAddr ) } } +void CServer::OnAboutToQuit() +{ + if ( IsRunning() ) + { + // Clean up + CleanShutdown(); + } + + // if server was registered at the central server, unregister on shutdown + if ( GetServerListEnabled() ) + { + UnregisterSlaveServer(); + } +} + +void CServer::OnShutdown ( int ) +{ + // This should trigger OnAboutToQuit + QCoreApplication::instance()->exit(); +} + + +void CServer::CleanShutdown() { + Mutex.lock(); + { + bool oldDAC = bDisconnectAllClients; + + // This is to prevent new connections + bDisconnectAllClients = true; + + // This is to disconnect all connected channels + for ( int i = 0; i < iMaxNumChannels; i++ ) + { + if ( vecChannels[i].IsConnected() ) + { + vecChannels[i].Disconnect(); + } + } + + // This should tell the jam recorder than the jam has ended... + Stop(); + + // Restore the value in case we are not exiting + bDisconnectAllClients = oldDAC; + } + Mutex.unlock(); +} + void CServer::Start() { // only start if not already running @@ -1273,7 +1328,7 @@ opus_custom_encoder_ctl ( CurOpusEncoder, Stop(); } - Q_UNUSED ( iUnused ); + Q_UNUSED ( iUnused ) } /// @brief Mix all audio data from all clients together. diff --git a/src/server.h b/src/server.h index d56a42c8..fc422c2c 100755 --- a/src/server.h +++ b/src/server.h @@ -36,6 +36,7 @@ #endif #include "global.h" #include "buffer.h" +#include "signalhandler.h" #include "socket.h" #include "channel.h" #include "util.h" @@ -257,6 +258,8 @@ protected: int GetNumberOfConnectedClients(); CVector CreateChannelList(); + CSignalHandler* pSignalHandler = CSignalHandler::getSingletonP(); + #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) virtual void CreateAndSendChanListForAllConChannels(); virtual void CreateAndSendChanListForThisChan ( const int iCurChanID ); @@ -303,6 +306,8 @@ protected: const CVector > vecvecsData, CVector& vecLevelsOut ); + void CleanShutdown(); + // do not use the vector class since CChannel does not have appropriate // copy constructor/operator CChannel vecChannels[MAX_NUM_CHANNELS]; @@ -455,6 +460,10 @@ public slots: void OnCLDisconnection ( CHostAddress InetAddr ); + void OnAboutToQuit(); + + void OnShutdown ( int ); + #if QT_VERSION < 0x50000 // MOC does not expand macros in Qt 4, so we cannot use QT_VERSION_CHECK(5, 0, 0) // CODE TAG: MAX_NUM_CHANNELS_TAG // make sure we have MAX_NUM_CHANNELS connections!!! diff --git a/src/serverdlg.cpp b/src/serverdlg.cpp index 7717e023..7df039ed 100755 --- a/src/serverdlg.cpp +++ b/src/serverdlg.cpp @@ -342,18 +342,6 @@ lvwClients->setMinimumHeight ( 140 ); Timer.start ( GUI_CONTRL_UPDATE_TIME ); } -void CServerDlg::closeEvent ( QCloseEvent* Event ) -{ - // if server was registered at the central server, unregister on shutdown - if ( pServer->GetServerListEnabled() ) - { - pServer->UnregisterSlaveServer(); - } - - // default implementation of this event handler routine - Event->accept(); -} - void CServerDlg::OnStartOnOSStartStateChanged ( int value ) { const bool bCurAutoStartMinState = ( value == Qt::Checked ); diff --git a/src/serverdlg.h b/src/serverdlg.h index febb44ef..da06e0c4 100755 --- a/src/serverdlg.h +++ b/src/serverdlg.h @@ -58,7 +58,6 @@ public: protected: virtual void changeEvent ( QEvent* pEvent ); - virtual void closeEvent ( QCloseEvent* Event ); void UpdateGUIDependencies(); void UpdateSystemTrayIcon ( const bool bIsActive ); diff --git a/src/signalhandler.cpp b/src/signalhandler.cpp new file mode 100755 index 00000000..648506e3 --- /dev/null +++ b/src/signalhandler.cpp @@ -0,0 +1,146 @@ +/******************************************************************************\ + * Copyright (c) 2020 + * + * Author(s): + * Peter L Jones + * + ****************************************************************************** + * + * This program 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 2 of the License, or (at your option) any later + * version. + * + * This program 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 + * this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +\******************************************************************************/ + +#include "signalhandler.h" + +class CSignalHandlerSingleton : public CSignalHandler { +public: + inline CSignalHandlerSingleton() : CSignalHandler() {} +}; +Q_GLOBAL_STATIC ( CSignalHandlerSingleton, singleton ) + +CSignalHandler::CSignalHandler() : pSignalBase ( CSignalBase::withSignalHandler ( this ) ) {} + +CSignalHandler::~CSignalHandler() = default; + +CSignalHandler* CSignalHandler::getSingletonP() { return singleton; } + +bool CSignalHandler::emitSignal ( int sigNum ) +{ + return QMetaObject::invokeMethod( singleton, "ShutdownSignal", Qt::QueuedConnection, Q_ARG( int, sigNum ) ); +} + +#ifndef _WIN32 +void CSignalHandler::OnSocketNotify( int socket ) +{ + int sigNum; + if ( ::read ( socket, &sigNum, sizeof ( int ) ) == sizeof ( int ) ) + { + emitSignal ( sigNum ); + } +} +#endif + +// ---------------------------------------------------------- + +CSignalBase::CSignalBase ( CSignalHandler* pSignalHandler ) : + pSignalHandler ( pSignalHandler ) +{ +} + +CSignalBase::~CSignalBase() = default; + +CSignalBase* CSignalBase::withSignalHandler ( CSignalHandler* pSignalHandler ) +{ +#ifdef _WIN32 + return new CSignalWin ( pSignalHandler ); +#else + return new CSignalUnix ( pSignalHandler ); +#endif +} + +#ifdef _WIN32 +CSignalWin::CSignalWin ( CSignalHandler* nPSignalHandler ) : + CSignalBase ( nPSignalHandler ), + lock ( QReadWriteLock::Recursive ) +{ + SetConsoleCtrlHandler ( signalHandler, true ); +} + +CSignalWin::~CSignalWin() { + SetConsoleCtrlHandler ( signalHandler, false ); +} + +QReadWriteLock* CSignalWin::getLock() const +{ + return &lock; +} + +BOOL WINAPI CSignalWin::signalHandler ( _In_ DWORD sigNum ) +{ + auto self = getSelf(); + QReadLocker lock ( self->getLock() ); + return self->pSignalHandler->emitSignal ( static_cast( sigNum ) ); +} + +#else +int CSignalUnix::socketPair[2]; + +CSignalUnix::CSignalUnix ( CSignalHandler* nPSignalHandler ) : + CSignalBase ( nPSignalHandler ) +{ + if ( ::socketpair ( AF_UNIX, SOCK_STREAM, 0, socketPair ) == 0 ) + { + socketNotifier = new QSocketNotifier ( socketPair[ 1 ], QSocketNotifier::Read ); + + QObject::connect ( socketNotifier, &QSocketNotifier::activated, nPSignalHandler, &CSignalHandler::OnSocketNotify ); + + socketNotifier->setEnabled ( true ); + + setSignalHandled ( SIGINT, true ); + setSignalHandled ( SIGTERM, true ); + } +} + +CSignalUnix::~CSignalUnix() { + setSignalHandled ( SIGINT, false ); + setSignalHandled ( SIGTERM, false ); +} + +QReadWriteLock* CSignalUnix::getLock() const { return nullptr; } + +bool CSignalUnix::setSignalHandled ( int sigNum, bool state ) +{ + struct sigaction sa; + sigemptyset ( &sa.sa_mask ); + + if ( state ) + { + sa.sa_handler = CSignalUnix::signalHandler; + sa.sa_flags |= SA_RESTART; + } + else + { + sa.sa_handler = SIG_DFL; + } + + return ::sigaction ( sigNum, &sa, nullptr ) == 0; +} + +void CSignalUnix::signalHandler ( int sigNum ) +{ + const auto res = ::write ( socketPair[ 0 ], &sigNum, sizeof ( int ) ); + Q_UNUSED ( res ); +} +#endif diff --git a/src/signalhandler.h b/src/signalhandler.h new file mode 100755 index 00000000..d1e571d7 --- /dev/null +++ b/src/signalhandler.h @@ -0,0 +1,140 @@ +/******************************************************************************\ + * Copyright (c) 2020 + * + * Author(s): + * Peter L Jones + * + ****************************************************************************** + * + * This program 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 2 of the License, or (at your option) any later + * version. + * + * This program 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 + * this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +\******************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#include +#endif + +class CSignalBase; + +class CSignalHandler : public QObject +{ + Q_OBJECT + + friend class CSignalBase; + friend class CSignalHandlerSingleton; + +public: + static CSignalHandler* getSingletonP(); + + bool emitSignal ( int ); + +#ifndef _WIN32 +public slots: + void OnSocketNotify ( int socket ); +#endif + +signals: + void ShutdownSignal ( int sigNum ); + +private: + QScopedPointer pSignalBase; + + explicit CSignalHandler(); + ~CSignalHandler() override; +}; + +// ---------------------------------------------------------- + +class CSignalBase +{ + Q_DISABLE_COPY ( CSignalBase ) + +public: + static CSignalBase* withSignalHandler ( CSignalHandler* ); + virtual ~CSignalBase(); + + virtual QReadWriteLock* getLock() const = 0; + + QSet sHandledSigNums; + +protected: + CSignalBase ( CSignalHandler* ); + + CSignalHandler* pSignalHandler; + + template + static T *getSelf() + { + return static_cast( CSignalHandler::getSingletonP()->pSignalBase.data() ); + } + +}; + +#ifdef _WIN32 + +class CSignalWin : public CSignalBase +{ +public: + CSignalWin ( CSignalHandler* ); + ~CSignalWin() override; + + virtual QReadWriteLock* getLock() const override; + +private: + mutable QReadWriteLock lock; + + static BOOL WINAPI signalHandler ( _In_ DWORD sigNum ); +}; + +#else + +class CSignalUnix : public CSignalBase +{ +public: + CSignalUnix ( CSignalHandler* ); + ~CSignalUnix() override; + + virtual QReadWriteLock* getLock() const override; + +private: + QSocketNotifier* socketNotifier = nullptr; + bool setSignalHandled ( int sigNum, bool state ); + + static int socketPair[2]; + static void signalHandler ( int sigNum ); +}; + +#endif