Skip to content

Commit

Permalink
Merge branch 'LoTWImportData' into testing_0.42
Browse files Browse the repository at this point in the history
  • Loading branch information
foldynl committed Jan 31, 2025
2 parents fbb70c7 + 6d83a21 commit 49eb083
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 83 deletions.
173 changes: 109 additions & 64 deletions logformat/LogFormat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -753,8 +753,20 @@ void LogFormat::runQSLImport(QSLFrom fromService)
{
FCT_IDENTIFICATION;

QSLMergeStat stats = {QStringList(), QStringList(), 0, 0, 0, 0};
auto reportFormatter = [&](const QDateTime &qsoTime,
const QString &callsign,
const QString &mode,
const QStringList addInfo = QStringList())
{
return QString("%0; %1; %2%3 %4").arg(qsoTime.isValid() ? qsoTime.toString(locale.formatDateShortWithYYYY()) : "-",
callsign,
mode,
(addInfo.size() > 0 ) ? ";" : "",
addInfo.join(", "));
};

static QRegularExpression reLeadingZero("^0+");
QSLMergeStat stats = {QStringList(), QStringList(), QStringList(), QStringList(), 0};
this->importStart();

QSqlTableModel model;
Expand All @@ -767,9 +779,9 @@ void LogFormat::runQSLImport(QSLFrom fromService)

if ( !this->importNext(QSLRecord) ) break;

stats.qsos_checked++;
stats.qsosDownloaded++;

if ( stats.qsos_checked % 100 == 0 )
if ( stats.qsosDownloaded % 100 == 0 )
{
emit importPosition(stream.pos());
}
Expand All @@ -789,7 +801,7 @@ void LogFormat::runQSLImport(QSLFrom fromService)
{
qWarning() << "Import does not contain field start_time or callsign or band or mode ";
qCDebug(runtime) << QSLRecord;
stats.qsos_errors++;
stats.errorQSLs.append(reportFormatter(start_time.toDateTime(), call.toString(), mode.toString()));
continue;
}

Expand All @@ -807,8 +819,7 @@ void LogFormat::runQSLImport(QSLFrom fromService)

if ( model.rowCount() != 1 )
{
stats.qsos_unmatched++;
stats.unmatchedQSLs.append(call.toString());
stats.unmatchedQSLs.append(reportFormatter(start_time.toDateTime(), call.toString(), mode.toString()));
continue;
}

Expand All @@ -821,72 +832,104 @@ void LogFormat::runQSLImport(QSLFrom fromService)
case LOTW:
{
/* https://lotw.arrl.org/lotw-help/developer-query-qsos-qsls/?lang=en */
if ( !QSLRecord.value("lotw_qsl_rcvd").toString().isEmpty() )
// always try to update contact from received QSL
if ( QSLRecord.value("qsl_rcvd").toString() == 'Y' ) // qsl_rcvd is OK because LoTW sends lotw_qsl_rcvd value in qsl_rcvd
{
if ( QSLRecord.value("qsl_rcvd") != originalRecord.value("lotw_qsl_rcvd")
&& QSLRecord.value("qsl_rcvd").toString() == 'Y' )
{
originalRecord.setValue("lotw_qsl_rcvd", QSLRecord.value("qsl_rcvd"));
QStringList updatedFields;
bool callUpdate = false;
bool newlyReceived = (QSLRecord.value("qsl_rcvd").toString() != originalRecord.value("lotw_qsl_rcvd").toString());

originalRecord.setValue("lotw_qslrdate", QSLRecord.value("qsl_rdate"));
qCDebug(runtime) << "Attempt to" << (newlyReceived ? "force " : "") << "update QSO" << call.toString()
<< band.toString() << start_time.toString();

Gridsquare dxNewGrid(QSLRecord.value("gridsquare").toString());

if ( dxNewGrid.isValid()
&& ( originalRecord.value("gridsquare").toString().isEmpty()
||
dxNewGrid.getGrid().contains(originalRecord.value("gridsquare").toString()))
)
auto conditionUpdate = [&](const QString &contactKey,
const QString &qslKey,
bool forceUpdate = false)
{
if ( !QSLRecord.value(qslKey).toString().isEmpty()
&& ( forceUpdate || originalRecord.value(contactKey).toString().isEmpty() ) )
{
Gridsquare myGrid(originalRecord.value("my_gridsquare").toString());

originalRecord.setValue("gridsquare", dxNewGrid.getGrid());

double distance;

if ( myGrid.distanceTo(dxNewGrid, distance) )
{
originalRecord.setValue("distance", QVariant(distance));
}
qCDebug(runtime) << "Updating:" << contactKey
<< "to" << QSLRecord.value(qslKey).toString()
<< (forceUpdate ? "force update" : "");
updatedFields.append(contactKey + "(" + QSLRecord.value(qslKey).toString() +")");
originalRecord.setValue(contactKey, QSLRecord.value(qslKey));
return true;
}
return false;
};

if ( !QSLRecord.value("credit_granted").toString().isEmpty() )
{
originalRecord.setValue("credit_granted", QSLRecord.value("credit_granted"));
}
auto conditionUpdateSpecial = [&](const QString &contactKey,
const QString &qslKey,
bool forceUpdate = false)
{
QString contactValue = originalRecord.value(contactKey).toString();
QString QSLValue = QSLRecord.value(qslKey).toString();
contactValue.remove(reLeadingZero);
QSLValue.remove(reLeadingZero);

if ( !QSLRecord.value("credit_submitted").toString().isEmpty() )
if ( !QSLValue.isEmpty()
&& ( forceUpdate || contactValue != QSLValue ) )
{
originalRecord.setValue("credit_submitted", QSLRecord.value("credit_submitted"));
qCDebug(runtime) << "Updating:" << contactKey
<< "from" << originalRecord.value(contactKey).toString()
<< "to" << QSLValue
<< (forceUpdate ? "force update" : "");
updatedFields.append(contactKey + "(" + QSLRecord.value(qslKey).toString() +")");
originalRecord.setValue(contactKey, QSLValue);
return true;
}
return false;
};

callUpdate |= conditionUpdate("lotw_qsl_rcvd", "qsl_rcvd", newlyReceived);
callUpdate |= conditionUpdate("lotw_qslrdate", "qsl_rdate", newlyReceived);
callUpdate |= conditionUpdate("credit_granted", "credit_granted", newlyReceived);
callUpdate |= conditionUpdate("credit_submitted", "credit_submitted", newlyReceived);
callUpdate |= conditionUpdate("pfx", "pfx", newlyReceived);
callUpdate |= conditionUpdate("iota", "iota", newlyReceived);
callUpdate |= conditionUpdate("vucc_grids", "vucc_grids", newlyReceived);
callUpdate |= conditionUpdate("state", "state", newlyReceived);
callUpdate |= conditionUpdate("cnty", "cnty", newlyReceived);
callUpdate |= conditionUpdateSpecial("ituz", "ituz", newlyReceived);
callUpdate |= conditionUpdateSpecial("cqz", "cqz", newlyReceived);

if ( originalRecord.value("qsl_rcvd_via").toString() != "E" )
{
qCDebug(runtime) << "Updating: qsl_rcvd_via from" << originalRecord.value("qsl_rcvd_via").toString() << "to E";
originalRecord.setValue("qsl_rcvd_via", "E");
updatedFields.append("qsl_rcvd_via (E)");
callUpdate |= true;
}

if ( !QSLRecord.value("pfx").toString().isEmpty() )
{
originalRecord.setValue("pfx", QSLRecord.value("pfx"));
}
const QString origGrig = originalRecord.value("gridsquare").toString();
const Gridsquare dxNewGrid(QSLRecord.value("gridsquare").toString());

if ( !QSLRecord.value("iota").toString().isEmpty() )
{
originalRecord.setValue("iota", QSLRecord.value("iota"));
}
if ( ( newlyReceived
|| origGrig.isEmpty()
|| ( origGrig.length() < QSLRecord.value("gridsquare").toString().length()
&& dxNewGrid.isValid()
&& dxNewGrid.getGrid().contains(origGrig) ) )
&& !dxNewGrid.getGrid().isEmpty() )
{
const Gridsquare myGrid(originalRecord.value("my_gridsquare").toString());

if ( !QSLRecord.value("vucc_grids").toString().isEmpty() )
{
originalRecord.setValue("vucc_grids", QSLRecord.value("vucc_grids"));
}
originalRecord.setValue("gridsquare", dxNewGrid.getGrid());

if ( !QSLRecord.value("state").toString().isEmpty() )
{
originalRecord.setValue("state", QSLRecord.value("state"));
}
double distance;

if ( !QSLRecord.value("cnty").toString().isEmpty() )
if ( myGrid.distanceTo(dxNewGrid, distance) )
{
originalRecord.setValue("cnty", QSLRecord.value("cnty"));
originalRecord.setValue("distance", QVariant(distance));
}
qCDebug(runtime) << "Updating: grid from " << origGrig << "to" << dxNewGrid.getGrid();
updatedFields.append("gridsquare (" + dxNewGrid.getGrid() +")");
callUpdate |= true;
}

originalRecord.setValue("qsl_rcvd_via", "E");

if ( callUpdate )
{
qCDebug(runtime) << "Calling update for" << call << band << mode << start_time << satName;
if ( !model.setRecord(0, originalRecord) )
{
qWarning() << "Cannot update a Contact record - " << model.lastError();
Expand All @@ -897,14 +940,15 @@ void LogFormat::runQSLImport(QSLFrom fromService)
{
qWarning() << "Cannot commit changes to Contact Table - " << model.lastError();
}
stats.qsos_updated++;
stats.newQSLs.append(call.toString());
if ( newlyReceived )
{
const DxccStatus status = Data::instance()->dxccStatus(originalRecord.value("dxcc").toInt(), band.toString(), mode.toString());
stats.newQSLs.append(reportFormatter(start_time.toDateTime(), call.toString(), mode.toString(), {tr("DXCC State:") + " " + Data::statusToText(status)}));
}
else
stats.updatedQSOs.append(reportFormatter(start_time.toDateTime(), call.toString(), mode.toString(), updatedFields));
}
}
else
{
qCInfo(runtime) << "Malformed Lotw Record " << QSLRecord;
}
break;
}

Expand All @@ -928,7 +972,8 @@ void LogFormat::runQSLImport(QSLFrom fromService)
APP_EQSL_AG (tag only present if sender has Authenticity Guaranteed status and then always Y)
GRIDSQUARE (tag only present if non-blank and at least 4 long)
*/

// LF: Since I consider this source unreliable, I will not update it here, as I do with LoTW
// try to update contact from received QSL only in case when contact != Y
if ( originalRecord.value("eqsl_qsl_rcvd").toString() != 'Y' )
{
originalRecord.setValue("eqsl_qsl_rcvd", QSLRecord.value("qsl_sent"));
Expand Down Expand Up @@ -971,8 +1016,8 @@ void LogFormat::runQSLImport(QSLFrom fromService)
{
qWarning() << "Cannot commit changes to Contact Table - " << model.lastError();
}
stats.qsos_updated++;
stats.newQSLs.append(call.toString());
const DxccStatus status = Data::instance()->dxccStatus(originalRecord.value("dxcc").toInt(), band.toString(), mode.toString());
stats.newQSLs.append(reportFormatter(start_time.toDateTime(), call.toString(), mode.toString(), {tr("DXCC State:") + " " + Data::statusToText(status)}));
}

break;
Expand Down
7 changes: 3 additions & 4 deletions logformat/LogFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ class QSqlRecord;

struct QSLMergeStat {
QStringList newQSLs;
QStringList updatedQSOs;
QStringList unmatchedQSLs;
int qsos_updated;
int qsos_checked;
int qsos_unmatched;
int qsos_errors;
QStringList errorQSLs;
int qsosDownloaded;
};

class LogFormat : public QObject {
Expand Down
48 changes: 33 additions & 15 deletions ui/QSLImportStatDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,39 @@ QSLImportStatDialog::QSLImportStatDialog(QSLMergeStat stats, QWidget *parent) :

ui->setupUi(this);

ui->updatedNumer->setText(QString::number(stats.qsos_updated));
ui->downloadedNumber->setText(QString::number(stats.qsos_checked));
ui->unmatchedNumber->setText(QString::number(stats.qsos_unmatched));
ui->errorsNumber->setText(QString::number(stats.qsos_errors));

ui->detailsText->moveCursor(QTextCursor::End);
ui->detailsText->insertPlainText (tr("New QSLs: ") + "\n");
ui->detailsText->moveCursor(QTextCursor::End);
ui->detailsText->insertPlainText (stats.newQSLs.join(", "));
ui->detailsText->moveCursor (QTextCursor::End);

ui->detailsText->insertPlainText("\n" + tr("Unmatched QSLs: ") + "\n");
ui->detailsText->moveCursor(QTextCursor::End);
ui->detailsText->insertPlainText (stats.unmatchedQSLs.join(", "));
ui->detailsText->moveCursor (QTextCursor::End);
ui->updatedNumer->setText(QString::number(stats.updatedQSOs.size() + stats.newQSLs.size()));
ui->downloadedNumber->setText(QString::number(stats.qsosDownloaded));
ui->unmatchedNumber->setText(QString::number(stats.unmatchedQSLs.size()));
ui->errorsNumber->setText(QString::number(stats.errorQSLs.size()));

if ( !stats.newQSLs.isEmpty() )
{
ui->detailsText->moveCursor(QTextCursor::End);
ui->detailsText->insertPlainText("*** " + tr("New QSLs: ") + "\n");
ui->detailsText->moveCursor(QTextCursor::End);
ui->detailsText->insertPlainText(stats.newQSLs.join("\n"));
ui->detailsText->moveCursor(QTextCursor::End);
ui->detailsText->insertPlainText("\n\n");
ui->detailsText->moveCursor(QTextCursor::End);
}

if ( !stats.updatedQSOs.isEmpty() )
{
ui->detailsText->insertPlainText("*** " + tr("Updated QSOs: ") + "\n");
ui->detailsText->moveCursor(QTextCursor::End);
ui->detailsText->insertPlainText(stats.updatedQSOs.join("\n"));
ui->detailsText->moveCursor(QTextCursor::End);
ui->detailsText->insertPlainText("\n\n");
ui->detailsText->moveCursor(QTextCursor::End);
}

if ( !stats.unmatchedQSLs.isEmpty() )
{
ui->detailsText->insertPlainText("*** " + tr("Unmatched QSLs: ") + "\n");
ui->detailsText->moveCursor(QTextCursor::End);
ui->detailsText->insertPlainText (stats.unmatchedQSLs.join("\n"));
ui->detailsText->moveCursor(QTextCursor::End);
}
}

QSLImportStatDialog::~QSLImportStatDialog()
Expand Down

0 comments on commit 49eb083

Please sign in to comment.