Add the core, contest, storage and format layers
Frequencies, bands, modes, callsigns, grid squares and the country file live in Nonemm.Core. Nonemm.Contests holds the scoring engine and CQ WW and CQ WPX. Nonemm.Storage writes N1MM's DXLOG schema, and Nonemm.Formats writes Cabrillo 3.0 and reads and writes ADIF. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
147
src/Nonemm.Storage/QsoColumns.cs
Normal file
147
src/Nonemm.Storage/QsoColumns.cs
Normal file
@@ -0,0 +1,147 @@
|
||||
using System.Globalization;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Nonemm.Core;
|
||||
|
||||
namespace Nonemm.Storage;
|
||||
|
||||
/// Turns a contact into N1MM's `DXLOG` columns and back. Points and multiplier
|
||||
/// flags are written because N1MM reads them, and ignored on the way back
|
||||
/// because this program works them out from the rules.
|
||||
internal static class QsoColumns
|
||||
{
|
||||
internal const string TimestampFormat = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
internal static readonly IReadOnlyList<string> Names =
|
||||
[
|
||||
"TS", "Call", "Freq", "QSXFreq", "Mode", "ContestName", "SNT", "RCV",
|
||||
"CountryPrefix", "StationPrefix", "QTH", "Name", "Comment", "NR", "Sect",
|
||||
"Prec", "CK", "ZN", "SentNr", "Points", "IsMultiplier1", "IsMultiplier2",
|
||||
"Power", "Band", "WPXPrefix", "Exchange1", "RadioNR", "ContestNR",
|
||||
"isMultiplier3", "MiscText", "IsRunQSO", "ContactType", "Run1Run2",
|
||||
"GridSquare", "Operator", "Continent", "RoverLocation", "RadioInterfaced",
|
||||
"NetworkedCompNr", "NetBiosName", "IsOriginal", "ID", "CLAIMEDQSO",
|
||||
];
|
||||
|
||||
internal static void Bind(SqliteCommand command, Qso qso)
|
||||
{
|
||||
command.Parameters.AddWithValue("@TS", qso.TimestampUtc.ToString(TimestampFormat, CultureInfo.InvariantCulture));
|
||||
command.Parameters.AddWithValue("@Call", qso.Call.Text);
|
||||
command.Parameters.AddWithValue("@Freq", qso.Frequency.Kilohertz);
|
||||
command.Parameters.AddWithValue("@QSXFreq", qso.QsxFrequency.Kilohertz);
|
||||
command.Parameters.AddWithValue("@Mode", qso.Mode.Name);
|
||||
command.Parameters.AddWithValue("@ContestName", qso.ContestName);
|
||||
command.Parameters.AddWithValue("@SNT", qso.SentReport);
|
||||
command.Parameters.AddWithValue("@RCV", qso.ReceivedReport);
|
||||
command.Parameters.AddWithValue("@CountryPrefix", qso.CountryPrefix);
|
||||
command.Parameters.AddWithValue("@StationPrefix", qso.StationPrefix);
|
||||
command.Parameters.AddWithValue("@QTH", qso.Qth);
|
||||
command.Parameters.AddWithValue("@Name", qso.Name);
|
||||
command.Parameters.AddWithValue("@Comment", qso.Comment);
|
||||
command.Parameters.AddWithValue("@NR", qso.ReceivedNumber);
|
||||
command.Parameters.AddWithValue("@Sect", qso.Section);
|
||||
command.Parameters.AddWithValue("@Prec", qso.Precedence);
|
||||
command.Parameters.AddWithValue("@CK", qso.Check);
|
||||
command.Parameters.AddWithValue("@ZN", qso.Zone);
|
||||
command.Parameters.AddWithValue("@SentNr", qso.SentNumber);
|
||||
command.Parameters.AddWithValue("@Points", qso.Points);
|
||||
command.Parameters.AddWithValue("@IsMultiplier1", qso.IsMultiplier1 ? 1 : 0);
|
||||
command.Parameters.AddWithValue("@IsMultiplier2", qso.IsMultiplier2 ? 1 : 0);
|
||||
command.Parameters.AddWithValue("@Power", qso.Power);
|
||||
command.Parameters.AddWithValue("@Band", qso.Band?.MegahertzLabel ?? 0.0);
|
||||
command.Parameters.AddWithValue("@WPXPrefix", qso.WpxPrefix);
|
||||
command.Parameters.AddWithValue("@Exchange1", qso.Exchange1);
|
||||
command.Parameters.AddWithValue("@RadioNR", qso.RadioNumber);
|
||||
command.Parameters.AddWithValue("@ContestNR", qso.ContestNumber);
|
||||
command.Parameters.AddWithValue("@isMultiplier3", qso.IsMultiplier3 ? 1 : 0);
|
||||
command.Parameters.AddWithValue("@MiscText", qso.MiscText);
|
||||
command.Parameters.AddWithValue("@IsRunQSO", qso.IsRunQso ? 1 : 0);
|
||||
command.Parameters.AddWithValue("@ContactType", qso.ContactType);
|
||||
command.Parameters.AddWithValue("@Run1Run2", qso.RunPosition);
|
||||
command.Parameters.AddWithValue("@GridSquare", qso.GridSquare);
|
||||
command.Parameters.AddWithValue("@Operator", qso.Operator);
|
||||
command.Parameters.AddWithValue("@Continent", qso.Continent);
|
||||
command.Parameters.AddWithValue("@RoverLocation", qso.RoverLocation);
|
||||
command.Parameters.AddWithValue("@RadioInterfaced", qso.IsRadioInterfaced ? 1 : 0);
|
||||
command.Parameters.AddWithValue("@NetworkedCompNr", qso.NetworkedComputerNumber);
|
||||
command.Parameters.AddWithValue("@NetBiosName", qso.StationName);
|
||||
command.Parameters.AddWithValue("@IsOriginal", qso.IsOriginal ? 1 : 0);
|
||||
command.Parameters.AddWithValue("@ID", qso.Id);
|
||||
command.Parameters.AddWithValue("@CLAIMEDQSO", qso.IsClaimed ? 1 : 0);
|
||||
}
|
||||
|
||||
internal static Qso Read(SqliteDataReader row) => new()
|
||||
{
|
||||
Id = Text(row, "ID"),
|
||||
TimestampUtc = Timestamp(row, "TS"),
|
||||
Call = Callsign.Parse(Text(row, "Call")),
|
||||
Frequency = Frequency.FromKilohertz(Number(row, "Freq")),
|
||||
QsxFrequency = Frequency.FromKilohertz(Number(row, "QSXFreq")),
|
||||
Mode = Modes.Parse(Text(row, "Mode")) ?? Modes.Digital,
|
||||
ContestName = Text(row, "ContestName"),
|
||||
ContestNumber = Integer(row, "ContestNR"),
|
||||
SentReport = Text(row, "SNT"),
|
||||
ReceivedReport = Text(row, "RCV"),
|
||||
SentNumber = Integer(row, "SentNr"),
|
||||
ReceivedNumber = Integer(row, "NR"),
|
||||
Section = Text(row, "Sect"),
|
||||
Precedence = Text(row, "Prec"),
|
||||
Check = Integer(row, "CK"),
|
||||
Zone = Integer(row, "ZN"),
|
||||
Exchange1 = Text(row, "Exchange1"),
|
||||
MiscText = Text(row, "MiscText"),
|
||||
Comment = Text(row, "Comment"),
|
||||
Name = Text(row, "Name"),
|
||||
Qth = Text(row, "QTH"),
|
||||
Power = Text(row, "Power"),
|
||||
GridSquare = Text(row, "GridSquare"),
|
||||
RoverLocation = Text(row, "RoverLocation"),
|
||||
CountryPrefix = Text(row, "CountryPrefix"),
|
||||
StationPrefix = Text(row, "StationPrefix"),
|
||||
WpxPrefix = Text(row, "WPXPrefix"),
|
||||
Continent = Text(row, "Continent"),
|
||||
IsRunQso = Flag(row, "IsRunQSO"),
|
||||
ContactType = Text(row, "ContactType"),
|
||||
RunPosition = Integer(row, "Run1Run2"),
|
||||
Operator = Text(row, "Operator"),
|
||||
RadioNumber = Integer(row, "RadioNR"),
|
||||
IsRadioInterfaced = Flag(row, "RadioInterfaced"),
|
||||
NetworkedComputerNumber = Integer(row, "NetworkedCompNr"),
|
||||
StationName = Text(row, "NetBiosName"),
|
||||
IsOriginal = Flag(row, "IsOriginal"),
|
||||
IsClaimed = Flag(row, "CLAIMEDQSO"),
|
||||
};
|
||||
|
||||
private static string Text(SqliteDataReader row, string column)
|
||||
{
|
||||
int at = row.GetOrdinal(column);
|
||||
return row.IsDBNull(at) ? "" : row.GetString(at);
|
||||
}
|
||||
|
||||
private static double Number(SqliteDataReader row, string column)
|
||||
{
|
||||
int at = row.GetOrdinal(column);
|
||||
return row.IsDBNull(at) ? 0 : row.GetDouble(at);
|
||||
}
|
||||
|
||||
private static int Integer(SqliteDataReader row, string column)
|
||||
{
|
||||
int at = row.GetOrdinal(column);
|
||||
return row.IsDBNull(at) ? 0 : (int)row.GetInt64(at);
|
||||
}
|
||||
|
||||
private static bool Flag(SqliteDataReader row, string column) => Integer(row, column) != 0;
|
||||
|
||||
/// N1MM writes seconds; older rows and other writers add fractions or a
|
||||
/// `T` separator, so anything round-trippable is accepted.
|
||||
private static DateTime Timestamp(SqliteDataReader row, string column)
|
||||
{
|
||||
string text = Text(row, column);
|
||||
return DateTime.TryParse(
|
||||
text,
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal,
|
||||
out DateTime parsed)
|
||||
? parsed
|
||||
: throw new FormatException($"contact timestamp '{text}' is not a date");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user