Skip to content

Commit

Permalink
adds pota export type
Browse files Browse the repository at this point in the history
Park activations are uploaded to pota.app with a specific set of requirements.
This change allows a user to directly export&generate the files to import into pota.app without
having to use a 3rd party tool to do transformations or manually fiddle with data.
  • Loading branch information
kyleboyle committed Nov 23, 2024
1 parent 7ae1a2c commit 3ca6106
Show file tree
Hide file tree
Showing 9 changed files with 259 additions and 17 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
7 changes: 7 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
3 changes: 2 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
139 changes: 139 additions & 0 deletions logformat/PotaAdiFormat.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#include "PotaAdiFormat.h"
#include <QDebug>
#include <QSqlField>
#include <QSqlRecord>
#include "core/debug.h"
#include <models/LogbookModel.h>

MODULE_IDENTIFICATION("qlog.logformat.potalogformat");

#define ALWAYS_PRESENT true

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

void PotaAdiFormat::setExportInfo(QFile &exportFile)
{
this->exportInfo = new QFileInfo(exportFile);
}

void PotaAdiFormat::exportContact(const QSqlRecord &sourceRecord, QMap<QString, QString> *applTags)
{
FCT_IDENTIFICATION;
if (this->exportInfo == nullptr) {
return;
}
// break single record into child activated park records
// assign records to files
QList<QSqlRecord> records = PotaAdiFormat::splitActivatedParks(sourceRecord);
for (QSqlRecord &record : records) {
duplicateField(record, "my_pota_ref", "my_sig");
record.field("my_sig").setValue(QString("POTA"));
duplicateField(record, "my_pota_ref", "my_sig_info");
if (!record.field("pota_ref").isNull()) {
duplicateField(record, "pota_ref", "sig_info");
duplicateField(record, "pota_ref", "sig");
record.field("sig").setValue(QString("POTA"));
}
AdiFormat *parkOut = this->getParkFile(record);
parkOut->exportContact(record, applTags);
// let parent do ADI export as normal to specified file
AdiFormat::exportContact(record, applTags);
}
}

AdiFormat *PotaAdiFormat::getParkFile(const QSqlRecord &record)
{
// https://docs.pota.app/docs/activator_reference/logging_made_easy.html#naming-your-files
// station_callsign@park#-yyyymmdd
QString parkFileName(record.field("station_callsign").value().toString() + "@"
+ record.field("my_sig_info").value().toString() + "-"
+ currentDate.toString("yyyyMMdd-hhmm") + ".adif");

if (!parkFormats.contains(parkFileName)) {
parkFiles[parkFileName] = new QFile(exportInfo->canonicalPath() + QDir::separator()
+ parkFileName);
if (!parkFiles[parkFileName]->open(QFile::WriteOnly | QFile::Text)) {
qCCritical(runtime) << "Could not open POTA park file for writing "
<< parkFiles[parkFileName]->fileName();
}
QTextStream *parkStream = new QTextStream(parkFiles[parkFileName]);
parkFormats[parkFileName] = new AdiFormat(*parkStream);
parkFormats[parkFileName]->exportStart();
}
qCDebug(runtime) << "using park file " << parkFileName << " is open? "
<< parkFiles[parkFileName]->isOpen();

return parkFormats[parkFileName];
}

QList<QSqlRecord> PotaAdiFormat::splitActivatedParks(const QSqlRecord &record)
{
FCT_IDENTIFICATION;
// can contain multiple parks as a csv:
// <MY_POTA_REF:40>K-0817,K-4566,K-4576,K-4573,K-4578@US-WY
QStringList activatedParks = record.field("my_pota_ref")
.value()
.toString()
.split(QRegularExpression("\\s*,\\s*"),
Qt::SplitBehaviorFlags::SkipEmptyParts);

if (activatedParks.length() <= 0) {
return QList<QSqlRecord>();
} else if (activatedParks.length() == 1) {
return QList<QSqlRecord>({record});
} else {
QList<QSqlRecord> records = QList<QSqlRecord>();
for (const QString &parkID : activatedParks) {
QSqlRecord single = QSqlRecord(record);
single.setValue("my_pota_ref", parkID);

// If this is a park to park - the remote park can also be a multi park
// activation. These must also be split into multiple records.
QSqlField parkToPark = record.field("pota_ref");
if (parkToPark.isNull() || !parkToPark.value().toString().contains(",")) {
records.append(single);
} else {
QStringList remoteParks
= parkToPark.value().toString().split(QRegularExpression("\\s*,\\s*"),
Qt::SplitBehaviorFlags::SkipEmptyParts);
for (const QString &remoteParkID : remoteParks) {
QSqlRecord remoteSingle = QSqlRecord(single);
remoteSingle.setValue("pota_ref", remoteParkID);
records.append(remoteSingle);
}
}
}
return records;
}
}

void PotaAdiFormat::exportEnd()
{
for (AdiFormat *parkFormat : parkFormats.values()) {
parkFormat->exportEnd();
}
}

void PotaAdiFormat::duplicateField(QSqlRecord &record,
const QString &fromFieldName,
const QString &toFieldName)
{
QSqlField dupped(record.field(fromFieldName));
record.remove(record.indexOf(toFieldName));
dupped.setName(toFieldName);
record.append(dupped);
}

PotaAdiFormat::~PotaAdiFormat()
{
FCT_IDENTIFICATION;
qDeleteAll(parkFormats.values());
parkFormats.clear();
qDeleteAll(parkFiles.values());
parkFiles.clear();
}
37 changes: 37 additions & 0 deletions logformat/PotaAdiFormat.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#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 setExportInfo(QFile &exportFile);
~PotaAdiFormat();

static QList<QSqlRecord> splitActivatedParks(const QSqlRecord &);

private:
QFileInfo *exportInfo;
QMap<QString, AdiFormat *> parkFormats;
QMap<QString, QFile *> parkFiles;
QDateTime currentDate;
AdiFormat *getParkFile(const QSqlRecord &record);
static void duplicateField(QSqlRecord &record,
const QString &fromFieldName,
const QString &toFieldName);
};

#endif // QLOG_LOGFORMAT_POTALOGFORMAT_H
32 changes: 25 additions & 7 deletions ui/ExportDialog.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#include <QFileDialog>
#include "ui/ExportDialog.h"
#include <QDebug>
#include <QFileDialog>
#include <QMessageBox>
#include <QSqlError>
#include "ui/ExportDialog.h"
#include "ui_ExportDialog.h"
#include <logformat/PotaAdiFormat.h>

#include "core/debug.h"
#include "models/SqlListModel.h"
Expand Down Expand Up @@ -142,14 +143,18 @@ void ExportDialog::runExport()

QTextStream out(&file);

LogFormat* format = LogFormat::open(ui->typeSelect->currentText(), out);
LogFormat *format = LogFormat::open(ui->typeSelect->currentText(), out);

if (!format)
{
qCritical() << "unknown log format";
return;
}

if (PotaAdiFormat *potaFormat = dynamic_cast<PotaAdiFormat *>(format)) {
potaFormat->setExportInfo(file);
}

if ( ui->dateRangeCheckBox->isChecked() )
format->setFilterDateRange(ui->startDateEdit->date(), ui->endDateEdit->date());

Expand Down Expand Up @@ -334,6 +339,7 @@ void ExportDialog::fillExportedColumnsCombo()

ui->exportedColumnsCombo->addItem(tr("All"), "all");
ui->exportedColumnsCombo->addItem(tr("Minimal"), "min");
ui->exportedColumnsCombo->addItem(tr("POTA"), "pota");
ui->exportedColumnsCombo->addItem(tr("QSL-specific"), "qsl");
ui->exportedColumnsCombo->addItem(tr("Custom 1"), "c1");
ui->exportedColumnsCombo->addItem(tr("Custom 2"), "c2");
Expand Down Expand Up @@ -402,6 +408,10 @@ void ExportDialog::exportedColumnsComboChanged(int index)
{
exportedColumns = settings.value("export/" + comboValue, QVariant::fromValue(qslColumns)).value<QSet<int>>();
}
else if ( comboValue == "pota" )
{
exportedColumns = potaColumns;
}
}
}

Expand All @@ -418,12 +428,20 @@ void ExportDialog::fillQSLSendViaCombo()
FCT_IDENTIFICATION;

QMapIterator<QString, QString> iter(Data::instance()->qslSentViaEnum);
int iter_index = 0;
while ( iter.hasNext() )
{
while (iter.hasNext()) {
iter.next();
ui->qslSendViaComboBox->addItem(iter.value(), iter.key());
iter_index++;
}
}

void ExportDialog::exportFormatChanged(const QString &format)
{
FCT_IDENTIFICATION;

if (format == "POTA") {
ui->exportedColumnsCombo->setCurrentIndex(ui->exportedColumnsCombo->findData("pota"));
} else {
ui->exportedColumnsCombo->setCurrentIndex(ui->exportedColumnsCombo->findData("all"));
}
}

Expand Down
19 changes: 18 additions & 1 deletion ui/ExportDialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public slots:
void exportedColumnStateChanged(int index, bool state);
void exportTypeChanged(int index);
void exportedColumnsComboChanged(int);

void exportFormatChanged(const QString &format);
private:
Ui::ExportDialog *ui;
LogLocale locale;
Expand All @@ -59,6 +59,23 @@ public slots:
LogbookModel::COLUMN_RST_SENT,
LogbookModel::COLUMN_RST_RCVD
};
const QSet<int> potaColumns{
LogbookModel::COLUMN_TIME_ON,
LogbookModel::COLUMN_CALL,
LogbookModel::COLUMN_OPERATOR,
LogbookModel::COLUMN_STATION_CALLSIGN,
LogbookModel::COLUMN_FREQUENCY,
LogbookModel::COLUMN_MODE,
LogbookModel::COLUMN_SUBMODE,
LogbookModel::COLUMN_MY_STATE,
LogbookModel::COLUMN_MY_COUNTRY,
LogbookModel::COLUMN_MY_POTA_REF,
LogbookModel::COLUMN_POTA_REF,
LogbookModel::COLUMN_MY_SIG,
LogbookModel::COLUMN_MY_SIG_INFO,
LogbookModel::COLUMN_SIG,
LogbookModel::COLUMN_SIG_INFO
};
LogbookModel logbookmodel;
QSettings settings;
const QList<QSqlRecord> qsos4export;
Expand Down
Loading

0 comments on commit 3ca6106

Please sign in to comment.