Skip to content

Commit

Permalink
Merge branch 'POTAExport' into testing_0.41
Browse files Browse the repository at this point in the history
  • Loading branch information
foldynl committed Dec 20, 2024
2 parents 5d3f4e3 + dc75707 commit af36d42
Show file tree
Hide file tree
Showing 9 changed files with 354 additions and 10 deletions.
2 changes: 2 additions & 0 deletions QLog.pro
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ SOURCES += \
logformat/CSVFormat.cpp \
logformat/JsonFormat.cpp \
logformat/LogFormat.cpp \
logformat/PotaAdiFormat.cpp \
models/AlertTableModel.cpp \
models/AwardsTableModel.cpp \
models/DxccTableModel.cpp \
Expand Down Expand Up @@ -241,6 +242,7 @@ HEADERS += \
logformat/CSVFormat.h \
logformat/JsonFormat.h \
logformat/LogFormat.h \
logformat/PotaAdiFormat.h \
models/AlertTableModel.h \
models/AwardsTableModel.h \
models/DxccTableModel.h \
Expand Down
1 change: 0 additions & 1 deletion logformat/AdiFormat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ void AdiFormat::exportStart()
{
FCT_IDENTIFICATION;

stream << "### QLog ADIF Export\n";
writeField("ADIF_VER", ALWAYS_PRESENT, ADIF_VERSION_STRING);
writeField("PROGRAMID", ALWAYS_PRESENT, PROGRAMID_STRING);
writeField("PROGRAMVERSION", ALWAYS_PRESENT, VERSION);
Expand Down
16 changes: 16 additions & 0 deletions logformat/LogFormat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "LogFormat.h"
#include "AdiFormat.h"
#include "AdxFormat.h"
#include "PotaAdiFormat.h"
#include "JsonFormat.h"
#include "CSVFormat.h"
#include "data/Data.h"
Expand Down Expand Up @@ -51,6 +52,9 @@ LogFormat* LogFormat::open(QString type, QTextStream& stream) {
else if (type == "cabrillo") {
return open(LogFormat::JSON, stream);
}
else if (type == "pota") {
return open(LogFormat::POTA, stream);
}
else {
return nullptr;
}
Expand All @@ -77,6 +81,9 @@ LogFormat* LogFormat::open(LogFormat::Type type, QTextStream& stream) {
case LogFormat::CABRILLO:
return nullptr;

case LogFormat::POTA:
return new PotaAdiFormat(stream);

default:
return nullptr;
}
Expand Down Expand Up @@ -149,6 +156,12 @@ void LogFormat::setUserFilter(const QString &value)
userFilter = value;
}

void LogFormat::setPotaOnly(bool only)
{
FCT_IDENTIFICATION;
filterPOTAOnly = only;
}

QString LogFormat::getWhereClause()
{
FCT_IDENTIFICATION;
Expand All @@ -173,6 +186,9 @@ QString LogFormat::getWhereClause()
whereClause << ( ( filterSendVia == " " ) ? "qsl_sent_via is NULL"
: "upper(qsl_sent_via) = upper(:qsl_sent_via)");

if ( filterPOTAOnly )
whereClause << QLatin1String("(my_pota_ref is not NULL OR pota_ref is not NULL OR lower(sig)='pota' OR lower(my_sig)='pota')");

if ( !userFilter.isEmpty() )
whereClause << QSOFilterManager::getWhereClause(userFilter);

Expand Down
5 changes: 4 additions & 1 deletion logformat/LogFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ class LogFormat : public QObject {
ADX,
CABRILLO,
JSON,
CSV
CSV,
POTA
};

enum QSLFrom {
Expand Down Expand Up @@ -66,6 +67,7 @@ class LogFormat : public QObject {
void setFilterSentPaperQSL(bool includeNo, bool includeIgnore, bool includeAlreadySent);
void setFilterSendVia(const QString &value);
void setUserFilter(const QString&value);
void setPotaOnly(bool only);
QString getWhereClause();
void bindWhereClause(QSqlQuery &);
void setExportedFields(const QStringList& fieldsList);
Expand Down Expand Up @@ -121,6 +123,7 @@ class LogFormat : public QObject {
QStringList whereClause;
QStringList exportedFields;
QString userFilter;
bool filterPOTAOnly = false;
bool updateDxcc = false;
duplicateQSOBehaviour (*duplicateQSOFunc)(QSqlRecord *, QSqlRecord *);
LogLocale locale;
Expand Down
206 changes: 206 additions & 0 deletions logformat/PotaAdiFormat.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
#include "PotaAdiFormat.h"
#include <QDebug>
#include <QSqlField>
#include <QSqlRecord>
#include "core/debug.h"
#include <models/LogbookModel.h>

MODULE_IDENTIFICATION("qlog.logformat.potalogformat");

PotaAdiFormat::PotaAdiFormat(QTextStream &stream) :
AdiFormat(stream),
currentDate(QDateTime::currentDateTime())
{
FCT_IDENTIFICATION;
}

void PotaAdiFormat::setExportDirectory(const QString &dir)
{
FCT_IDENTIFICATION;

exportDir = dir;
}

void PotaAdiFormat::exportContact(const QSqlRecord &sourceRecord, QMap<QString, QString> *applTags)
{
FCT_IDENTIFICATION;

if ( exportDir.isEmpty() || !isValidPotaRecord(sourceRecord) )
return;

QSqlRecord inputRecord(sourceRecord);

preparePotaField(inputRecord, "my_pota_ref", "my_sig_info", "my_sig");
preparePotaField(inputRecord, "pota_ref", "sig_info", "sig");

QList<QSqlRecord> expandedRecords({inputRecord});

// Expand records based on specific fields - one record for the specific POTA Ref
expandParkRecord(expandedRecords, "my_sig_info");
expandParkRecord(expandedRecords, "sig_info");

for ( const QSqlRecord &record : static_cast<const QList<QSqlRecord>&>(expandedRecords) )
{
const QString &mySig = record.value("my_sig").toString().toLower();

// export Activator Log
if ( mySig == "pota" )
{
if ( AdiFormat *parkOut = this->getActivatorParkFormatter(record) )
{
parkOut->exportContact(record, applTags);
continue;
}
}
// export Hunter Log
else if ( record.value("sig").toString().toLower() == "pota" && mySig.isEmpty() )
AdiFormat::exportContact(record, applTags);
}
}

AdiFormat *PotaAdiFormat::getActivatorParkFormatter(const QSqlRecord &record)
{
FCT_IDENTIFICATION;

// https://docs.pota.app/docs/activator_reference/logging_made_easy.html#naming-your-files
// station_callsign@park#-yyyymmdd
const QString parkFileName = QString("%1@%2-%3.adif")
.arg(record.value("station_callsign").toString(),
record.value("my_sig_info").toString(),
currentDate.toString("yyyyMMdd-hhmm"));

if ( parkFormatters.contains(parkFileName) )
{
qCDebug(runtime) << "Using park file " << parkFileName;
return parkFormatters[parkFileName]->formatter;
}

ParkFormatter *parkFormatter = new ParkFormatter();

parkFormatter->file = new QFile(exportDir + QDir::separator() + parkFileName);
if ( !parkFormatter->file->open(QFile::WriteOnly | QFile::Text) )
{
qCWarning(runtime) << "Could not open POTA park file for writing "
<< exportDir + QDir::separator() + parkFileName;
delete parkFormatter;
return nullptr;
}

parkFormatter->stream = new QTextStream(parkFormatter->file);
if ( !parkFormatter->stream )
{
qCWarning(runtime) << "Cannot allocate QTextStream";
delete parkFormatter;
return nullptr;
}

parkFormatter->formatter = new AdiFormat(*parkFormatter->stream);
if ( !parkFormatter->formatter )
{
qCWarning(runtime) << "Cannot allocate AdifFormatter";
delete parkFormatter;
return nullptr;
}

parkFormatters[parkFileName] = parkFormatter;
parkFormatter->formatter->exportStart();
return parkFormatter->formatter;
}

void PotaAdiFormat::expandParkRecord(QList<QSqlRecord> &inputList, const QString &columnName)
{
FCT_IDENTIFICATION;

QList<QSqlRecord> expandedNewRecords;

// can contain multiple parks as a csv:
// <MY_POTA_REF:40>K-0817,K-4566,K-4576,K-4573,K-4578@US-WY

for ( auto it = inputList.cbegin(); it != inputList.cend(); it++ )
{
QStringList activatedParks = it->value(columnName).toString().split(",",
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
Qt::SplitBehaviorFlags::SkipEmptyParts);
#else
QString::SkipEmptyParts);
#endif
for ( QString &str : activatedParks )
str = str.trimmed();

if ( activatedParks.size() <= 1 )
{
expandedNewRecords.append(*it);
continue;
}

for ( const QString &parkID : static_cast<const QStringList&>(activatedParks) )
{
QSqlRecord newRecord(*it);
newRecord.setValue(columnName, parkID);
expandedNewRecords.append(newRecord);
}
}

inputList.swap(expandedNewRecords);
}

void PotaAdiFormat::exportEnd()
{
FCT_IDENTIFICATION;

const QList<ParkFormatter*> &formatters = parkFormatters.values();

for ( ParkFormatter *parkFormat : formatters)
parkFormat->formatter->exportEnd();
}

void PotaAdiFormat::moveFieldValue(QSqlRecord &record,
const QString &fromFieldName,
const QString &toFieldName)
{
FCT_IDENTIFICATION;

QSqlField dupped(record.field(fromFieldName));
record.remove(record.indexOf(fromFieldName));
record.remove(record.indexOf(toFieldName));
dupped.setName(toFieldName);
record.append(dupped);
}

bool PotaAdiFormat::isValidPotaRecord(const QSqlRecord &record) const
{
FCT_IDENTIFICATION;

auto isPotaWithEmptyInfo = [](const QString &sig, const QString &info)
{
return sig == "pota" && info.isEmpty();
};

const QString &sigField = record.value("sig").toString().toLower();
const QString &mysigField = record.value("my_sig").toString().toLower();

return !record.value("my_pota_ref").toString().isEmpty()
|| !record.value("pota_ref").toString().isEmpty()
|| (sigField == "pota" && !isPotaWithEmptyInfo(sigField, record.value("sig_info").toString()))
|| (mysigField == "pota" && !isPotaWithEmptyInfo(mysigField, record.value("my_sig_info").toString()));
}

void PotaAdiFormat::preparePotaField(QSqlRecord &record, const QString &fromField, const QString &toField, const QString &toFieldSig)
{
FCT_IDENTIFICATION;

if (record.value(fromField).toString().isEmpty())
return;

moveFieldValue(record, fromField, toField);
record.setValue(toFieldSig, "POTA");

}

PotaAdiFormat::~PotaAdiFormat()
{
FCT_IDENTIFICATION;

qDeleteAll(parkFormatters);
parkFormatters.clear();
}
54 changes: 54 additions & 0 deletions logformat/PotaAdiFormat.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#ifndef QLOG_LOGFORMAT_POTALOGFORMAT_H
#define QLOG_LOGFORMAT_POTALOGFORMAT_H
#include "AdiFormat.h"

/*
* A specialized case of ADI export, where each activated park gets T'd into its
* own file with some denormalization and values set to satisfy the pota.app
* upload processes.
*/
class PotaAdiFormat : public AdiFormat
{
public:
explicit PotaAdiFormat(QTextStream &stream);

virtual void exportContact(const QSqlRecord &,
QMap<QString, QString> *applTags = nullptr) override;
virtual void exportEnd() override;

virtual bool importNext(QSqlRecord &) override { return false; }

void setExportDirectory(const QString &dir);
~PotaAdiFormat();

private:
QString exportDir;
QDateTime currentDate;

struct ParkFormatter
{
AdiFormat *formatter = nullptr;
QFile *file = nullptr;
QTextStream *stream = nullptr;

~ParkFormatter()
{
if ( formatter ) delete formatter;
if ( stream ) delete stream;
if ( file ) delete file;
}
};

QHash<QString, ParkFormatter *> parkFormatters;

AdiFormat *getActivatorParkFormatter(const QSqlRecord &record);
void moveFieldValue(QSqlRecord &record,
const QString &fromFieldName,
const QString &toFieldName);
bool isValidPotaRecord(const QSqlRecord &record) const;
void preparePotaField(QSqlRecord &record, const QString &refField,
const QString &infoField, const QString &sigField);
void expandParkRecord(QList<QSqlRecord> &inputList, const QString &columnName);
};

#endif // QLOG_LOGFORMAT_POTALOGFORMAT_H
Loading

0 comments on commit af36d42

Please sign in to comment.