Files
sdrangel/plugins/samplesource/fileinput/fileinput.cpp
T
Jon BenistonandClaude Opus 5 60db861797 Fix QString leak in the web API settings and report formatters
The generated SWG setters overwrite the string pointer without deleting
what is already there, and every webapiSettingsGet and webapiReportGet
calls init() first, which has allocated one. Each call therefore leaked a
QString per string field. Measured at 117 bytes per settings GET for the
Satellite Tracker, over 20,000 requests.

Assign in place when the pointer is already set, which is what these same
functions have always done for title and reverseAPIAddress. 394 sites
across webapiFormatChannelSettings, webapiFormatDeviceSettings,
webapiFormatFeatureSettings and the three report equivalents.

The webapiReverseSend* functions are deliberately left alone: they build a
fresh SWG object whose constructor leaves the pointers null, so passing a
new QString is correct there.

After the change the same measurement is flat, at 7 bytes per GET.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 16:54:42 +01:00

854 lines
28 KiB
C++

///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015-2020, 2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2019 Vort <vvort@yandex.ru> //
// Copyright (C) 2021, 2023 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
// //
// 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 as version 3 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <string.h>
#include <errno.h>
#include <QDebug>
#include <QNetworkReply>
#include <QBuffer>
#include "SWGDeviceSettings.h"
#include "SWGFileInputSettings.h"
#include "SWGDeviceState.h"
#include "SWGDeviceReport.h"
#include "util/simpleserializer.h"
#include "dsp/dspcommands.h"
#include "dsp/dspdevicesourceengine.h"
#include "dsp/filerecord.h"
#include "dsp/wavfilerecord.h"
#include "device/deviceapi.h"
#include "fileinput.h"
#include "fileinputworker.h"
MESSAGE_CLASS_DEFINITION(FileInput::MsgConfigureFileInput, Message)
MESSAGE_CLASS_DEFINITION(FileInput::MsgConfigureFileSourceName, Message)
MESSAGE_CLASS_DEFINITION(FileInput::MsgConfigureFileInputWork, Message)
MESSAGE_CLASS_DEFINITION(FileInput::MsgConfigureFileSourceSeek, Message)
MESSAGE_CLASS_DEFINITION(FileInput::MsgConfigureFileInputStreamTiming, Message)
MESSAGE_CLASS_DEFINITION(FileInput::MsgStartStop, Message)
MESSAGE_CLASS_DEFINITION(FileInput::MsgPlayPause, Message)
MESSAGE_CLASS_DEFINITION(FileInput::MsgReportFileSourceAcquisition, Message)
MESSAGE_CLASS_DEFINITION(FileInput::MsgReportFileInputStreamData, Message)
MESSAGE_CLASS_DEFINITION(FileInput::MsgReportFileInputStreamTiming, Message)
MESSAGE_CLASS_DEFINITION(FileInput::MsgReportHeaderCRC, Message)
FileInput::FileInput(DeviceAPI *deviceAPI) :
m_deviceAPI(deviceAPI),
m_settings(),
m_fileInputWorker(nullptr),
m_deviceDescription("FileInput"),
m_sampleRate(48000),
m_sampleSize(0),
m_centerFrequency(435000000),
m_dataStartPos(0),
m_recordLengthMuSec(0),
m_startingTimeStamp(0)
{
m_sampleFifo.setLabel(m_deviceDescription);
m_deviceAPI->setNbSourceStreams(1);
qDebug("FileInput::FileInput: device source engine: %p", m_deviceAPI->getDeviceSourceEngine());
qDebug("FileInput::FileInput: device source engine message queue: %p", m_deviceAPI->getDeviceEngineInputMessageQueue());
qDebug("FileInput::FileInput: device source: %p", m_deviceAPI->getDeviceSourceEngine()->getSource());
m_networkManager = new QNetworkAccessManager();
QObject::connect(
m_networkManager,
&QNetworkAccessManager::finished,
this,
&FileInput::networkManagerFinished
);
m_masterTimer.setTimerType(Qt::PreciseTimer);
m_masterTimer.start(50);
}
FileInput::~FileInput()
{
m_masterTimer.stop();
QObject::disconnect(
m_networkManager,
&QNetworkAccessManager::finished,
this,
&FileInput::networkManagerFinished
);
delete m_networkManager;
stop();
}
void FileInput::destroy()
{
delete this;
}
void FileInput::openFileStream()
{
//stopInput();
#ifdef ANDROID
if (m_inputFile.isOpen()) {
m_inputFile.close();
}
m_inputFile.setFileName(m_settings.m_fileName);
m_inputFile.open(QIODevice::ReadOnly | QIODevice::ExistingOnly);
quint64 fileSize = (quint64) m_inputFile.size();
#else
if (m_ifstream.is_open()) {
m_ifstream.close();
}
#ifdef Q_OS_WIN
m_ifstream.open(m_settings.m_fileName.toStdWString().c_str(), std::ios::binary | std::ios::ate);
#else
m_ifstream.open(m_settings.m_fileName.toStdString().c_str(), std::ios::binary | std::ios::ate);
#endif
quint64 fileSize = m_ifstream.tellg();
#endif
if (m_settings.m_fileName.endsWith(".wav"))
{
WavFileRecord::Header header;
#ifdef ANDROID
m_inputFile.seek(0);
bool headerOK = WavFileRecord::readHeader(m_inputFile, header);
#else
m_ifstream.seekg(0, std::ios_base::beg);
bool headerOK = WavFileRecord::readHeader(m_ifstream, header);
#endif
m_sampleRate = header.m_sampleRate;
if (header.m_auxiHeader.m_size > 0)
{
// Some WAV files written by SDR tools have auxi header
m_centerFrequency = header.m_auxi.m_centerFreq;
m_startingTimeStamp = header.getStartTime().toMSecsSinceEpoch();
}
else
{
// Attempt to extract start time and frequency from filename
QDateTime startTime;
if (WavFileRecord::getStartTime(m_settings.m_fileName, startTime)) {
m_startingTimeStamp = startTime.toMSecsSinceEpoch();
}
WavFileRecord::getCenterFrequency(m_settings.m_fileName, m_centerFrequency);
}
m_sampleSize = header.m_bitsPerSample;
if (headerOK && (m_sampleRate > 0) && (m_sampleSize > 0))
{
#ifdef ANDROID
m_dataStartPos = m_inputFile.pos();
#else
m_dataStartPos = m_ifstream.tellg();
#endif
m_recordLengthMuSec = ((fileSize - m_dataStartPos) * 1000000UL) / ((m_sampleSize == 24 ? 8 : 4) * m_sampleRate);
}
else
{
qCritical("FileInput::openFileStream: broken or unsupported format of .wav file");
m_recordLengthMuSec = 0;
}
if (getMessageQueueToGUI())
{
MsgReportHeaderCRC *report = MsgReportHeaderCRC::create(headerOK);
getMessageQueueToGUI()->push(report);
}
}
else if (fileSize > sizeof(FileRecord::Header))
{
FileRecord::Header header;
#ifdef ANDROID
m_inputFile.seek(0);
bool crcOK = FileRecord::readHeader(m_inputFile, header);
#else
m_ifstream.seekg(0,std::ios_base::beg);
bool crcOK = FileRecord::readHeader(m_ifstream, header);
#endif
m_dataStartPos = sizeof(FileRecord::Header);
m_sampleRate = header.sampleRate;
m_centerFrequency = header.centerFrequency;
m_startingTimeStamp = header.startTimeStamp;
m_sampleSize = header.sampleSize;
QString crcHex = QString("%1").arg(header.crc32 , 0, 16);
if (crcOK && (m_sampleRate > 0) && (m_sampleSize > 0))
{
qDebug("FileInput::openFileStream: CRC32 OK for header: %s", qPrintable(crcHex));
m_recordLengthMuSec = ((fileSize - m_dataStartPos) * 1000000UL) / ((m_sampleSize == 24 ? 8 : 4) * m_sampleRate);
}
else if (!crcOK)
{
qCritical("FileInput::openFileStream: bad CRC32 for header: %s", qPrintable(crcHex));
m_recordLengthMuSec = 0;
}
else
{
qCritical("FileInput::openFileStream: invalid header");
m_recordLengthMuSec = 0;
}
if (getMessageQueueToGUI())
{
MsgReportHeaderCRC *report = MsgReportHeaderCRC::create(crcOK);
getMessageQueueToGUI()->push(report);
}
}
else
{
m_recordLengthMuSec = 0;
}
qDebug() << "FileInput::openFileStream: " << m_settings.m_fileName.toStdString().c_str()
<< " fileSize: " << fileSize << " bytes"
<< " length: " << m_recordLengthMuSec << " microseconds"
<< " sample rate: " << m_sampleRate << " S/s"
<< " center frequency: " << m_centerFrequency << " Hz"
<< " sample size: " << m_sampleSize << " bits";
if (getMessageQueueToGUI())
{
DSPSignalNotification *notif = new DSPSignalNotification(m_sampleRate, m_centerFrequency);
getMessageQueueToGUI()->push(notif);
MsgReportFileInputStreamData *report = MsgReportFileInputStreamData::create(m_sampleRate,
m_sampleSize,
m_centerFrequency,
m_startingTimeStamp,
m_recordLengthMuSec); // file stream data
getMessageQueueToGUI()->push(report);
}
if (m_recordLengthMuSec == 0) {
#ifdef ANDROID
m_inputFile.close();
#else
m_ifstream.close();
#endif
}
}
void FileInput::seekFileStream(int seekMillis)
{
QMutexLocker mutexLocker(&m_mutex);
if (
#ifdef ANDROID
m_inputFile.isOpen()
#else
m_ifstream.is_open()
#endif
&& m_fileInputWorker && !m_fileInputWorker->isRunning())
{
quint64 seekPoint = ((m_recordLengthMuSec * seekMillis) / 1000) * m_sampleRate;
seekPoint /= 1000000UL;
m_fileInputWorker->setSamplesCount(seekPoint);
seekPoint *= (m_sampleSize == 24 ? 8 : 4);
#ifdef ANDROID
m_inputFile.seek(seekPoint + m_dataStartPos);
#else
m_ifstream.clear();
m_ifstream.seekg(seekPoint + m_dataStartPos, std::ios::beg);
#endif
}
}
void FileInput::init()
{
DSPSignalNotification *notif = new DSPSignalNotification(m_sampleRate, m_centerFrequency);
m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif);
}
bool FileInput::start()
{
#ifdef ANDROID
if (!m_inputFile.isOpen())
#else
if (!m_ifstream.is_open())
#endif
{
qWarning("FileInput::start: file not open. not starting");
return false;
}
QMutexLocker mutexLocker(&m_mutex);
qDebug() << "FileInput::start";
#ifdef ANDROID
m_inputFile.seek(0);
#else
if (m_ifstream.tellg() != (std::streampos)0)
{
m_ifstream.clear();
m_ifstream.seekg(m_dataStartPos, std::ios::beg);
}
#endif
if (!m_sampleFifo.setSize(m_settings.m_accelerationFactor * m_sampleRate * sizeof(Sample)))
{
qCritical("Could not allocate SampleFifo");
return false;
}
#ifdef ANDROID
m_fileInputWorker = new FileInputWorker(&m_inputFile, &m_sampleFifo, m_masterTimer, &m_inputMessageQueue);
#else
m_fileInputWorker = new FileInputWorker(&m_ifstream, &m_sampleFifo, m_masterTimer, &m_inputMessageQueue);
#endif
m_fileInputWorker->moveToThread(&m_fileInputWorkerThread);
m_fileInputWorker->setSampleRateAndSize(m_settings.m_accelerationFactor * m_sampleRate, m_sampleSize); // Fast Forward: 1 corresponds to live. 1/2 is half speed, 2 is double speed
startWorker();
m_deviceDescription = "FileInput";
mutexLocker.unlock();
qDebug("FileInput::startInput: started");
if (getMessageQueueToGUI())
{
MsgReportFileSourceAcquisition *report = MsgReportFileSourceAcquisition::create(true); // acquisition on
getMessageQueueToGUI()->push(report);
}
return true;
}
void FileInput::stop()
{
qDebug() << "FileInput::stop";
QMutexLocker mutexLocker(&m_mutex);
if (m_fileInputWorker)
{
stopWorker();
delete m_fileInputWorker;
m_fileInputWorker = nullptr;
}
m_deviceDescription.clear();
if (getMessageQueueToGUI())
{
MsgReportFileSourceAcquisition *report = MsgReportFileSourceAcquisition::create(false); // acquisition off
getMessageQueueToGUI()->push(report);
}
}
void FileInput::startWorker()
{
m_fileInputWorker->startWork();
m_fileInputWorkerThread.start();
}
void FileInput::stopWorker()
{
m_fileInputWorker->stopWork();
m_fileInputWorkerThread.quit();
m_fileInputWorkerThread.wait();
}
QByteArray FileInput::serialize() const
{
return m_settings.serialize();
}
bool FileInput::deserialize(const QByteArray& data)
{
bool success = true;
if (!m_settings.deserialize(data))
{
m_settings.resetToDefaults();
success = false;
}
MsgConfigureFileInput* message = MsgConfigureFileInput::create(m_settings, QList<QString>(), true);
m_inputMessageQueue.push(message);
if (getMessageQueueToGUI())
{
MsgConfigureFileInput* messageToGUI = MsgConfigureFileInput::create(m_settings, QList<QString>(), true);
getMessageQueueToGUI()->push(messageToGUI);
}
return success;
}
const QString& FileInput::getDeviceDescription() const
{
return m_deviceDescription;
}
int FileInput::getSampleRate() const
{
return m_sampleRate;
}
quint64 FileInput::getCenterFrequency() const
{
return m_centerFrequency;
}
void FileInput::setCenterFrequency(qint64 centerFrequency)
{
FileInputSettings settings = m_settings;
m_centerFrequency = centerFrequency;
MsgConfigureFileInput* message = MsgConfigureFileInput::create(m_settings, QList<QString>{"centerFrequency"}, false);
m_inputMessageQueue.push(message);
if (getMessageQueueToGUI())
{
MsgConfigureFileInput* messageToGUI = MsgConfigureFileInput::create(m_settings, QList<QString>{"centerFrequency"}, false);
getMessageQueueToGUI()->push(messageToGUI);
}
}
quint64 FileInput::getStartingTimeStamp() const
{
return m_startingTimeStamp;
}
bool FileInput::handleMessage(const Message& message)
{
if (MsgConfigureFileInput::match(message))
{
MsgConfigureFileInput& conf = (MsgConfigureFileInput&) message;
applySettings(conf.getSettings(), conf.getSettingsKeys(), conf.getForce());
return true;
}
else if (MsgConfigureFileSourceName::match(message))
{
MsgConfigureFileSourceName& conf = (MsgConfigureFileSourceName&) message;
m_settings.m_fileName = conf.getFileName();
openFileStream();
return true;
}
else if (MsgConfigureFileInputWork::match(message))
{
MsgConfigureFileInputWork& conf = (MsgConfigureFileInputWork&) message;
bool working = conf.isWorking();
if (m_fileInputWorker)
{
if (working) {
startWorker();
} else {
stopWorker();
}
}
return true;
}
else if (MsgConfigureFileSourceSeek::match(message))
{
MsgConfigureFileSourceSeek& conf = (MsgConfigureFileSourceSeek&) message;
int seekMillis = conf.getMillis();
seekFileStream(seekMillis);
return true;
}
else if (MsgConfigureFileInputStreamTiming::match(message))
{
MsgReportFileInputStreamTiming *report;
if (m_fileInputWorker)
{
if (getMessageQueueToGUI())
{
report = MsgReportFileInputStreamTiming::create(m_fileInputWorker->getSamplesCount());
getMessageQueueToGUI()->push(report);
}
}
return true;
}
else if (MsgStartStop::match(message))
{
MsgStartStop& cmd = (MsgStartStop&) message;
qDebug() << "FileInput::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop");
if (cmd.getStartStop())
{
if (m_deviceAPI->initDeviceEngine()) {
m_deviceAPI->startDeviceEngine();
}
}
else
{
m_deviceAPI->stopDeviceEngine();
}
if (m_settings.m_useReverseAPI) {
webapiReverseSendStartStop(cmd.getStartStop());
}
return true;
}
else if (FileInputWorker::MsgReportEOF::match(message))
{
qDebug() << "FileInput::handleMessage: MsgReportEOF";
stopWorker();
if (getMessageQueueToGUI())
{
MsgReportFileInputStreamTiming *report = MsgReportFileInputStreamTiming::create(m_fileInputWorker->getSamplesCount());
getMessageQueueToGUI()->push(report);
}
if (m_settings.m_loop)
{
seekFileStream(0);
startWorker();
}
else
{
if (getMessageQueueToGUI())
{
MsgPlayPause *report = MsgPlayPause::create(false);
getMessageQueueToGUI()->push(report);
}
}
return true;
}
else
{
return false;
}
}
bool FileInput::applySettings(const FileInputSettings& settings, const QList<QString>& settingsKeys, bool force)
{
qDebug() << "FileInput::applySettings: force: " << force << settings.getDebugString(settingsKeys, force);
if (settingsKeys.contains("accelerationFactor") || force)
{
if (m_fileInputWorker)
{
QMutexLocker mutexLocker(&m_mutex);
if (!m_sampleFifo.setSize(m_settings.m_accelerationFactor * m_sampleRate * sizeof(Sample))) {
qCritical("FileInput::applySettings: could not reallocate sample FIFO size to %lu",
m_settings.m_accelerationFactor * m_sampleRate * sizeof(Sample));
}
m_fileInputWorker->setSampleRateAndSize(settings.m_accelerationFactor * m_sampleRate, m_sampleSize); // Fast Forward: 1 corresponds to live. 1/2 is half speed, 2 is double speed
}
}
if (settings.m_useReverseAPI)
{
bool fullUpdate = (settingsKeys.contains("useReverseAPI") && settings.m_useReverseAPI) ||
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIDeviceIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}
if (force) {
m_settings = settings;
} else {
m_settings.applySettings(settingsKeys, settings);
}
// Open the file if there isn't a GUI which will open it
if ((m_guiMessageQueue == nullptr) && settingsKeys.contains("fileName") && !m_settings.m_fileName.isEmpty()) {
openFileStream();
}
return true;
}
int FileInput::webapiSettingsGet(
SWGSDRangel::SWGDeviceSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setFileInputSettings(new SWGSDRangel::SWGFileInputSettings());
response.getFileInputSettings()->init();
webapiFormatDeviceSettings(response, m_settings);
return 200;
}
int FileInput::webapiSettingsPutPatch(
bool force,
const QStringList& deviceSettingsKeys,
SWGSDRangel::SWGDeviceSettings& response, // query + response
QString& errorMessage)
{
(void) errorMessage;
FileInputSettings settings = m_settings;
webapiUpdateDeviceSettings(settings, deviceSettingsKeys, response);
MsgConfigureFileInput *msg = MsgConfigureFileInput::create(settings, deviceSettingsKeys, force);
m_inputMessageQueue.push(msg);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureFileInput *msgToGUI = MsgConfigureFileInput::create(settings, deviceSettingsKeys, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatDeviceSettings(response, settings);
return 200;
}
void FileInput::webapiUpdateDeviceSettings(
FileInputSettings& settings,
const QStringList& deviceSettingsKeys,
SWGSDRangel::SWGDeviceSettings& response)
{
if (deviceSettingsKeys.contains("title")) {
settings.m_title = *response.getFileInputSettings()->getTitle();
}
if (deviceSettingsKeys.contains("fileName")) {
settings.m_fileName = *response.getFileInputSettings()->getFileName();
}
if (deviceSettingsKeys.contains("accelerationFactor")) {
settings.m_accelerationFactor = response.getFileInputSettings()->getAccelerationFactor();
}
if (deviceSettingsKeys.contains("loop")) {
settings.m_loop = response.getFileInputSettings()->getLoop() != 0;
}
if (deviceSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getFileInputSettings()->getUseReverseApi() != 0;
}
if (deviceSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getFileInputSettings()->getReverseApiAddress();
}
if (deviceSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getFileInputSettings()->getReverseApiPort();
}
if (deviceSettingsKeys.contains("reverseAPIDeviceIndex")) {
settings.m_reverseAPIDeviceIndex = response.getFileInputSettings()->getReverseApiDeviceIndex();
}
}
int FileInput::webapiRunGet(
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage)
{
(void) errorMessage;
m_deviceAPI->getDeviceEngineStateStr(*response.getState());
return 200;
}
int FileInput::webapiRun(
bool run,
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage)
{
(void) errorMessage;
m_deviceAPI->getDeviceEngineStateStr(*response.getState());
MsgStartStop *message = MsgStartStop::create(run);
m_inputMessageQueue.push(message);
if (getMessageQueueToGUI()) // forward to GUI if any
{
MsgStartStop *msgToGUI = MsgStartStop::create(run);
getMessageQueueToGUI()->push(msgToGUI);
}
return 200;
}
int FileInput::webapiReportGet(
SWGSDRangel::SWGDeviceReport& response,
QString& errorMessage)
{
(void) errorMessage;
response.setFileInputReport(new SWGSDRangel::SWGFileInputReport());
response.getFileInputReport()->init();
webapiFormatDeviceReport(response);
return 200;
}
void FileInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const FileInputSettings& settings)
{
if (response.getFileInputSettings()->getTitle()) {
*response.getFileInputSettings()->getTitle() = settings.m_title;
} else {
response.getFileInputSettings()->setTitle(new QString(settings.m_title));
}
if (response.getFileInputSettings()->getFileName()) {
*response.getFileInputSettings()->getFileName() = settings.m_fileName;
} else {
response.getFileInputSettings()->setFileName(new QString(settings.m_fileName));
}
response.getFileInputSettings()->setAccelerationFactor(settings.m_accelerationFactor);
response.getFileInputSettings()->setLoop(settings.m_loop ? 1 : 0);
response.getFileInputSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getFileInputSettings()->getReverseApiAddress()) {
*response.getFileInputSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getFileInputSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getFileInputSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getFileInputSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex);
}
void FileInput::webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response)
{
qint64 t_sec = 0;
qint64 t_msec = 0;
quint64 samplesCount = 0;
if (m_fileInputWorker) {
samplesCount = m_fileInputWorker->getSamplesCount();
}
if (m_sampleRate > 0)
{
t_sec = samplesCount / m_sampleRate;
t_msec = (samplesCount - (t_sec * m_sampleRate)) * 1000 / m_sampleRate;
}
QTime t(0, 0, 0, 0);
t = t.addSecs(t_sec);
t = t.addMSecs(t_msec);
if (response.getFileInputReport()->getElapsedTime()) {
*response.getFileInputReport()->getElapsedTime() = t.toString("HH:mm:ss.zzz");
} else {
response.getFileInputReport()->setElapsedTime(new QString(t.toString("HH:mm:ss.zzz")));
}
qint64 startingTimeStampMsec = m_startingTimeStamp;
QDateTime dt = QDateTime::fromMSecsSinceEpoch(startingTimeStampMsec);
dt = dt.addSecs(t_sec);
dt = dt.addMSecs(t_msec);
if (response.getFileInputReport()->getAbsoluteTime()) {
*response.getFileInputReport()->getAbsoluteTime() = dt.toString("yyyy-MM-dd HH:mm:ss.zzz");
} else {
response.getFileInputReport()->setAbsoluteTime(new QString(dt.toString("yyyy-MM-dd HH:mm:ss.zzz")));
}
QTime recordLength(0, 0, 0, 0);
recordLength = recordLength.addMSecs(m_recordLengthMuSec / 1000UL);
if (response.getFileInputReport()->getDurationTime()) {
*response.getFileInputReport()->getDurationTime() = recordLength.toString("HH:mm:ss.zzz");
} else {
response.getFileInputReport()->setDurationTime(new QString(recordLength.toString("HH:mm:ss.zzz")));
}
if (response.getFileInputReport()->getFileName()) {
*response.getFileInputReport()->getFileName() = m_settings.m_fileName;
} else {
response.getFileInputReport()->setFileName(new QString(m_settings.m_fileName));
}
response.getFileInputReport()->setSampleRate(m_sampleRate);
response.getFileInputReport()->setSampleSize(m_sampleSize);
}
void FileInput::webapiReverseSendSettings(const QList<QString>& deviceSettingsKeys, const FileInputSettings& settings, bool force)
{
SWGSDRangel::SWGDeviceSettings *swgDeviceSettings = new SWGSDRangel::SWGDeviceSettings();
swgDeviceSettings->setDirection(0); // single Rx
swgDeviceSettings->setOriginatorIndex(m_deviceAPI->getDeviceSetIndex());
swgDeviceSettings->setDeviceHwType(new QString("FileInput"));
swgDeviceSettings->setFileInputSettings(new SWGSDRangel::SWGFileInputSettings());
SWGSDRangel::SWGFileInputSettings *swgFileInputSettings = swgDeviceSettings->getFileInputSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (deviceSettingsKeys.contains("accelerationFactor") || force) {
swgFileInputSettings->setAccelerationFactor(settings.m_accelerationFactor);
}
if (deviceSettingsKeys.contains("loop") || force) {
swgFileInputSettings->setLoop(settings.m_loop);
}
if (deviceSettingsKeys.contains("fileName") || force) {
swgFileInputSettings->setFileName(new QString(settings.m_fileName));
}
QString deviceSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/device/settings")
.arg(settings.m_reverseAPIAddress)
.arg(settings.m_reverseAPIPort)
.arg(settings.m_reverseAPIDeviceIndex);
m_networkRequest.setUrl(QUrl(deviceSettingsURL));
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QBuffer *buffer = new QBuffer();
buffer->open((QBuffer::ReadWrite));
buffer->write(swgDeviceSettings->asJson().toUtf8());
buffer->seek(0);
// Always use PATCH to avoid passing reverse API settings
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
buffer->setParent(reply);
delete swgDeviceSettings;
}
void FileInput::webapiReverseSendStartStop(bool start)
{
SWGSDRangel::SWGDeviceSettings *swgDeviceSettings = new SWGSDRangel::SWGDeviceSettings();
swgDeviceSettings->setDirection(0); // single Rx
swgDeviceSettings->setOriginatorIndex(m_deviceAPI->getDeviceSetIndex());
swgDeviceSettings->setDeviceHwType(new QString("FileInput"));
QString deviceSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/device/run")
.arg(m_settings.m_reverseAPIAddress)
.arg(m_settings.m_reverseAPIPort)
.arg(m_settings.m_reverseAPIDeviceIndex);
m_networkRequest.setUrl(QUrl(deviceSettingsURL));
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QBuffer *buffer = new QBuffer();
buffer->open((QBuffer::ReadWrite));
buffer->write(swgDeviceSettings->asJson().toUtf8());
buffer->seek(0);
QNetworkReply *reply;
if (start) {
reply = m_networkManager->sendCustomRequest(m_networkRequest, "POST", buffer);
} else {
reply = m_networkManager->sendCustomRequest(m_networkRequest, "DELETE", buffer);
}
buffer->setParent(reply);
delete swgDeviceSettings;
}
void FileInput::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "FileInput::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("FileInput::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}