jamulus/src/serverlogging.cpp

455 lines
15 KiB
C++
Raw Normal View History

2013-01-23 11:41:13 +01:00
/******************************************************************************\
2018-03-09 22:13:02 +01:00
* Copyright (c) 2004-2018
2014-01-05 17:52:38 +01:00
*
* Author(s):
* Volker Fischer
*
******************************************************************************
*
* 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
*
2013-01-23 11:41:13 +01:00
\******************************************************************************/
#include "serverlogging.h"
/* Implementation *************************************************************/
CHistoryGraph::CHistoryGraph() :
sFileName ( "" ),
bDoHistory ( false ),
vHistoryDataFifo ( NUM_ITEMS_HISTORY ),
PlotCanvasRect ( 0, 0, 600, 450 ), // defines total size of graph
iNumTicksX ( 0 ), // just an initialization value, will be overwritten
iYAxisStart ( 0 ),
iYAxisEnd ( 24 ),
iNumTicksY ( 5 ),
iGridFrameOffset ( 10 ),
iGridWidthWeekend ( 3 ), // should be an odd value
iTextOffsetToGrid ( 3 ),
iXAxisTextHeight ( 22 ),
iMarkerSizeNewCon ( 11 ),
iMarkerSizeServSt ( 8 ),
AxisFont ( "Arial", 12 ),
iTextOffsetX ( 18 ),
PlotBackgroundColor ( Qt::white ), // background
PlotFrameColor ( Qt::black ), // frame
PlotGridColor ( Qt::gray ), // grid
PlotTextColor ( Qt::black ), // text
PlotMarkerNewColor ( Qt::darkCyan ), // marker for new connection
PlotMarkerNewLocalColor ( Qt::blue ), // marker for new local connection
PlotMarkerStopColor ( Qt::red ), // marker for server stop
PlotPixmap ( 1, 1, QImage::Format_RGB32 )
{
// generate plot grid frame rectangle
PlotGridFrame.setRect ( PlotCanvasRect.x() + iGridFrameOffset,
PlotCanvasRect.y() + iGridFrameOffset,
PlotCanvasRect.width() - 2 * iGridFrameOffset,
PlotCanvasRect.height() - 2 * iGridFrameOffset - iXAxisTextHeight );
// scale pixmap to correct size
PlotPixmap = PlotPixmap.scaled (
PlotCanvasRect.width(), PlotCanvasRect.height() );
// Connections -------------------------------------------------------------
QObject::connect ( &TimerDailyUpdate, SIGNAL ( timeout() ),
this, SLOT ( OnTimerDailyUpdate() ) );
}
void CHistoryGraph::Start ( const QString& sNewFileName )
{
if ( !sNewFileName.isEmpty() )
{
// save file name
sFileName = sNewFileName;
// set enable flag
bDoHistory = true;
// enable timer (update once a day)
TimerDailyUpdate.start ( 3600000 * 24 );
// initial update (empty graph)
Update();
}
}
void CHistoryGraph::DrawFrame ( const int iNewNumTicksX )
{
int i;
// store number of x-axis ticks (number of days we want to draw
// the history for
iNumTicksX = iNewNumTicksX;
// clear base pixmap for new plotting
PlotPixmap.fill ( PlotBackgroundColor.rgb() ); // fill background
// create painter
QPainter PlotPainter ( &PlotPixmap );
// Create actual plot region (grid frame) ----------------------------------
PlotPainter.setPen ( PlotFrameColor );
PlotPainter.drawRect ( PlotGridFrame );
// calculate step for x-axis ticks so that we get the desired number of
// ticks -> 5 ticks
// TODO the following equation does not work as expected but results are acceptable
// we want to have "floor ( iNumTicksX / 5 )" which is the same as
// "iNumTicksX / 5" since "iNumTicksX" is an integer variable
const int iXAxisTickStep = iNumTicksX / 5 + 1;
// grid (ticks) for x-axis
dXSpace = static_cast<double> ( PlotGridFrame.width() ) / ( iNumTicksX + 1 );
for ( i = 0; i < iNumTicksX; i++ )
{
int iBottomExtraTickLen = 0;
const int iCurX = PlotGridFrame.x() + static_cast<int> ( dXSpace * ( i + 1 ) );
const QDate curXAxisDate = curDate.addDays ( i - iNumTicksX + 1 );
// text (print only every "iXAxisTickStep" tick)
if ( !( i % iXAxisTickStep ) )
{
PlotPainter.setPen ( PlotTextColor );
PlotPainter.setFont ( AxisFont );
PlotPainter.drawText (
QPoint ( iCurX - iTextOffsetX,
PlotGridFrame.bottom() + iXAxisTextHeight + iTextOffsetToGrid ),
curXAxisDate.toString ( "dd.MM." ) );
iBottomExtraTickLen = 5;
}
// regular grid
PlotPainter.setPen ( PlotGridColor );
PlotPainter.drawLine ( iCurX, 1 + PlotGridFrame.y(),
iCurX, PlotGridFrame.bottom() + iBottomExtraTickLen );
// different grid width for weekends (overwrite regular grid)
if ( ( curXAxisDate.dayOfWeek() == 6 ) || // check for Saturday
( curXAxisDate.dayOfWeek() == 7 ) ) // check for Sunday
{
const int iGridWidthWeekendHalf = iGridWidthWeekend / 2;
PlotPainter.setPen ( QPen ( PlotGridColor, iGridWidthWeekend ) );
PlotPainter.drawLine ( iCurX, 1 + PlotGridFrame.y() + iGridWidthWeekendHalf,
iCurX, PlotGridFrame.bottom() - iGridWidthWeekendHalf );
}
}
// grid (ticks) for y-axis, draw iNumTicksY - 2 grid lines and
// iNumTicksY - 1 text labels (the lowest grid line is the grid frame)
iYSpace = PlotGridFrame.height() / ( iNumTicksY - 1 );
for ( i = 0; i < ( iNumTicksY - 1 ); i++ )
{
const int iCurY = PlotGridFrame.y() + iYSpace * ( i + 1 );
// text
PlotPainter.setPen ( PlotTextColor );
PlotPainter.setFont ( AxisFont );
PlotPainter.drawText ( QPoint (
PlotGridFrame.x() + iTextOffsetToGrid,
iCurY - iTextOffsetToGrid ),
QString ( "%1:00" ).arg (
( iYAxisEnd - iYAxisStart ) / ( iNumTicksY - 1 ) *
( ( iNumTicksY - 2 ) - i ) ) );
// grid (do not overwrite frame)
if ( i < ( iNumTicksY - 2 ) )
{
PlotPainter.setPen ( PlotGridColor );
PlotPainter.drawLine ( PlotGridFrame.x(), iCurY,
PlotGridFrame.right(), iCurY );
}
}
}
void CHistoryGraph::AddMarker ( const SHistoryData& curHistoryData )
{
// calculate x-axis offset (difference of days compared to
// current date)
const int iXAxisOffs =
curDate.daysTo ( curHistoryData.DateTime.date() );
// check range, if out of range, do not plot anything
if ( -iXAxisOffs > ( iNumTicksX - 1 ) )
{
return;
}
// calculate y-axis offset (consider hours and minutes)
const double dYAxisOffs = 24 - curHistoryData.DateTime.time().hour() -
static_cast<double> ( curHistoryData.DateTime.time().minute() ) / 60;
// calculate the actual point in the graph (in pixels)
const QPoint curPoint (
PlotGridFrame.x() + static_cast<int> ( dXSpace * ( iNumTicksX + iXAxisOffs ) ),
PlotGridFrame.y() + static_cast<int> ( static_cast<double> (
PlotGridFrame.height() ) / ( iYAxisEnd - iYAxisStart ) * dYAxisOffs ) );
// create painter for plot
QPainter PlotPainter ( &PlotPixmap );
// we use different markers for new connection and server stop items
switch ( curHistoryData.Type )
{
case HIT_SERVER_STOP:
PlotPainter.setPen ( QPen ( QBrush ( PlotMarkerStopColor ),
iMarkerSizeServSt ) );
break;
case HIT_LOCAL_CONNECTION:
PlotPainter.setPen ( QPen ( QBrush ( PlotMarkerNewLocalColor ),
iMarkerSizeNewCon ) );
break;
case HIT_REMOTE_CONNECTION:
PlotPainter.setPen ( QPen ( QBrush ( PlotMarkerNewColor ),
iMarkerSizeNewCon ) );
break;
}
PlotPainter.drawPoint ( curPoint );
}
void CHistoryGraph::Save ( const QString sFileName )
{
// save plot as a file
PlotPixmap.save ( sFileName, "JPG", 90 );
}
void CHistoryGraph::Add ( const QDateTime& newDateTime,
const QHostAddress ClientInetAddr )
{
if ( bDoHistory )
{
// add element to history, distinguish between a local connection
// and a remote connection
if ( ( ClientInetAddr == QHostAddress ( "127.0.0.1" ) ) ||
( ClientInetAddr.toString().left ( 7 ).compare ( "192.168" ) == 0 ) )
{
// local connection
Add ( newDateTime, CHistoryGraph::HIT_LOCAL_CONNECTION );
}
else
{
// remote connection
Add ( newDateTime, CHistoryGraph::HIT_REMOTE_CONNECTION );
}
}
}
void CHistoryGraph::Add ( const QDateTime& newDateTime,
const EHistoryItemType curType )
{
if ( bDoHistory )
{
// create and add new element in FIFO
SHistoryData curHistoryData;
curHistoryData.DateTime = newDateTime;
curHistoryData.Type = curType;
vHistoryDataFifo.Add ( curHistoryData );
}
}
void CHistoryGraph::Update()
{
if ( bDoHistory )
{
int i;
// store current date for reference
curDate = QDate::currentDate();
// get oldest date in history
QDate oldestDate = curDate.addDays ( 1 ); // one day in the future
const int iNumItemsForHistory = vHistoryDataFifo.Size();
for ( i = 0; i < iNumItemsForHistory; i++ )
{
// only use valid dates
if ( vHistoryDataFifo[i].DateTime.date().isValid() )
{
if ( vHistoryDataFifo[i].DateTime.date() < oldestDate )
{
oldestDate = vHistoryDataFifo[i].DateTime.date();
}
}
}
const int iNumDaysInHistory = -curDate.daysTo ( oldestDate ) + 1;
// draw frame of the graph
DrawFrame ( iNumDaysInHistory );
// add markers
for ( i = 0; i < iNumItemsForHistory; i++ )
{
// only use valid dates
if ( vHistoryDataFifo[i].DateTime.date().isValid() )
{
AddMarker ( vHistoryDataFifo[i] );
}
2013-01-23 11:41:13 +01:00
}
// save graph as picture in file
Save ( sFileName );
}
}
// Server logging --------------------------------------------------------------
CServerLogging::~CServerLogging()
{
// close logging file of open
if ( File.isOpen() )
{
File.close();
}
}
void CServerLogging::Start ( const QString& strLoggingFileName )
{
// open file
File.setFileName ( strLoggingFileName );
if ( File.open ( QIODevice::Append | QIODevice::Text ) )
{
bDoLogging = true;
}
}
void CServerLogging::EnableHistory ( const QString& strHistoryFileName )
{
HistoryGraph.Start ( strHistoryFileName );
}
void CServerLogging::AddNewConnection ( const QHostAddress& ClientInetAddr )
{
// logging of new connected channel
const QString strLogStr = CurTimeDatetoLogString() + ", " +
ClientInetAddr.toString() + ", connected";
Add recording support with Reaper Project generation Includes the following changes * Initial .gitignore Administrative * Fix up warning message * Not all Windows file systems are case insensitive Bugfixes * (Qt5) Use QCoreApplication for headless Possible solution to get the application to run as a headless server but it loses the nice history graph, so not ideal. * Avoid ESC closing chat Because ESC shouldn't close the chat window. Or the main app window. * Add console logging support for Windows Whilst looking for the headless support, I found this idea for Windows logging. New improved version. This makes far fewer changes. ---- * Add recording support with Reaper Project generation The main feature! * New -r option to enable recording of PCM files and conversion to Reaper RPP with WAV files * New -R option to set the directory in which to create recording sessions You need to specify the -R option, there's no default... so I guess -r and -R could be combined. * New -T option to convert a session directory with PCM files into a Reaper RPP with WAV files You can use -T on "failed" sessions, if the -r option captures the PCMs but the RPP converter doesn't run for some reaon. (It was useful during development, maybe less so once things seem stable.) The recorder is implemented as a new thread with queuing from the main "real time" server thread. When a new client connects or if its audio format changes (e.g. mono to stereo), a new RIFF WAVE file is started. Each frame of decompressed audio for each client written out as LPCM to the file. When the client disconnects, the RIFF WAVE headers are updated to reflect the file length. Once all clients disconnect, the session is considered ended and a Reaper RPP file is written.
2019-04-03 19:12:45 +02:00
QTextStream* tsConsoleStream = (new ConsoleWriterFactory())->get();
(*tsConsoleStream) << strLogStr << endl; // on console
2013-01-23 11:41:13 +01:00
*this << strLogStr; // in log file
// add element to history
HistoryGraph.Add ( QDateTime::currentDateTime(), ClientInetAddr );
}
void CServerLogging::AddServerStopped()
{
const QString strLogStr = CurTimeDatetoLogString() + ",, server stopped "
"-------------------------------------";
Add recording support with Reaper Project generation Includes the following changes * Initial .gitignore Administrative * Fix up warning message * Not all Windows file systems are case insensitive Bugfixes * (Qt5) Use QCoreApplication for headless Possible solution to get the application to run as a headless server but it loses the nice history graph, so not ideal. * Avoid ESC closing chat Because ESC shouldn't close the chat window. Or the main app window. * Add console logging support for Windows Whilst looking for the headless support, I found this idea for Windows logging. New improved version. This makes far fewer changes. ---- * Add recording support with Reaper Project generation The main feature! * New -r option to enable recording of PCM files and conversion to Reaper RPP with WAV files * New -R option to set the directory in which to create recording sessions You need to specify the -R option, there's no default... so I guess -r and -R could be combined. * New -T option to convert a session directory with PCM files into a Reaper RPP with WAV files You can use -T on "failed" sessions, if the -r option captures the PCMs but the RPP converter doesn't run for some reaon. (It was useful during development, maybe less so once things seem stable.) The recorder is implemented as a new thread with queuing from the main "real time" server thread. When a new client connects or if its audio format changes (e.g. mono to stereo), a new RIFF WAVE file is started. Each frame of decompressed audio for each client written out as LPCM to the file. When the client disconnects, the RIFF WAVE headers are updated to reflect the file length. Once all clients disconnect, the session is considered ended and a Reaper RPP file is written.
2019-04-03 19:12:45 +02:00
QTextStream* tsConsoleStream = (new ConsoleWriterFactory())->get();
(*tsConsoleStream) << strLogStr << endl; // on console
2013-01-23 11:41:13 +01:00
*this << strLogStr; // in log file
// add element to history and update on server stop
HistoryGraph.Add ( QDateTime::currentDateTime(),
CHistoryGraph::HIT_SERVER_STOP );
HistoryGraph.Update();
}
void CServerLogging::operator<< ( const QString& sNewStr )
{
if ( bDoLogging )
{
// append new line in logging file
QTextStream out ( &File );
out << sNewStr << endl;
File.flush();
}
}
void CServerLogging::ParseLogFile ( const QString& strFileName )
{
// open file for reading
QFile LogFile ( strFileName );
LogFile.open ( QIODevice::ReadOnly | QIODevice::Text );
QTextStream inStream ( &LogFile );
// read all content from file
while ( !inStream.atEnd() )
{
// get a new line from log file
QString strCurLine = inStream.readLine();
// parse log file line
QStringList strlistCurLine = strCurLine.split( "," );
// check number of separated strings condition
if ( strlistCurLine.size() == 4 )
{
// first entry
QDate curDate =
QDate::fromString ( strlistCurLine.at ( 0 ).trimmed(),
"d.M.yyyy" );
// second entry
QTime curTime =
QTime::fromString ( strlistCurLine.at ( 1 ).trimmed(),
"hh:mm:ss" );
if ( curDate.isValid() && curTime.isValid() )
{
QDateTime curDateTime ( curDate, curTime );
// check if server stop or new client connection
QString strAddress = strlistCurLine.at ( 2 ).trimmed();
if ( strAddress.isEmpty() )
{
// server stop
HistoryGraph.Add ( curDateTime,
CHistoryGraph::HIT_SERVER_STOP );
}
else
{
QHostAddress curAddress;
// third entry is IP address
if ( curAddress.setAddress ( strlistCurLine.at ( 2 ).trimmed() ) )
{
// new client connection
HistoryGraph.Add ( curDateTime, curAddress );
}
}
}
}
}
HistoryGraph.Update();
}
QString CServerLogging::CurTimeDatetoLogString()
{
// time and date to string conversion
const QDateTime curDateTime = QDateTime::currentDateTime();
// format date and time output according to "3.9.2006, 11:38:08"
return QString().setNum ( curDateTime.date().day() ) + "." +
QString().setNum ( curDateTime.date().month() ) + "." +
QString().setNum ( curDateTime.date().year() ) + ", " +
curDateTime.time().toString();
}