Files
Nonemm/src/Nonemm.App/Windows/StationRow.cs
T
erikbrockoandClaude Opus 5 2a21c772ad Resynchronize a log with the other stations, and rescore it
A station that was away — a laptop that lost the network, one that was
switched off — comes back missing whatever the others logged while it was
gone. N1MM fills that in by walking the log an hour at a time, and this is
that walk, on the same messages:

| Message | What it says |
|---|---|
| `CHECKSUM` | here is what I hold for your contacts in this hour, and the contest and callsign I am logging |
| `DELETEQS` | drop your copies of my contacts in that hour, I am sending them again |
| `RESYNCQSO` | one of those contacts |
| `CONFIRMED` | here is what the hour hashes to at my end — which also asks for the next hour |

Each station is the authority on the contacts it made, so an hour that
disagrees is replaced rather than merged. The answer to one block asks for
the next, and the walk ends when the block starts after the end of the
period. The Resynchronize tab counts the hours that agreed, the hours that
were sent again and the hours that still disagreed, which are N1MM's
columns.

**`LogChecksum`** is N1MM's: a CRC32 over the `DXLOG` columns of the block,
ordered by time and then callsign, leaving out what each station works out
for itself — points, the three multiplier flags, the country prefix, the
continent, the WPX prefix and `IsOriginal`. Two stations may disagree about
those and still hold the same log. A checksum that disagrees for any other
reason costs one block of traffic, which is what a resync is for.

**The link still holds no log.** It asks one through `ContactLog` for the
contacts of a computer in a block of time, and `SessionContactLog` answers
on the window thread, because that is where the log is touched.

**Every contact logged here now carries the computer that made it.** N1MM
stamps `NetBiosName` and `NetworkedCompNr` on every row and this program
did not, so its own contacts belonged to no station and a resync could not
ask for them.

**Rescore** writes the points and multiplier flags of a period back to the
database. The log in memory scores itself whenever it changes; the stored
rows do not, and N1MM reads them rather than working them out. A contact
that arrives from another station after the one that took a multiplier
leaves those columns behind. A station can also ask the others to rescore
when its resync finishes, which is the flag on the end of `CHECKSUM`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdAYHcdRktqKry7nk414TU
2026-09-03 21:52:09 +00:00

107 lines
3.8 KiB
C#

using Avalonia.Media;
using Nonemm.App.Theming;
using Nonemm.Network;
namespace Nonemm.App.Windows;
/// One line of the network status window, in N1MM's columns. The station
/// itself holds the raw values; this turns them into the text of a cell.
public sealed class StationRow
{
private readonly NetworkedStation station;
private readonly DateTime now;
public StationRow(NetworkedStation station, DateTime now)
{
this.station = station;
this.now = now;
}
public NetworkedStation Station => station;
public string Computer => station.StationNumber > 0
? $"{station.ComputerName} ({station.StationNumber})"
: station.ComputerName;
public string IpAddress => station.Port > 0 && station.Address.Length > 0
? $"{station.Address}:{station.Port}"
: station.Address;
/// The frequency this station is passing contacts on, and who is on it.
public string Pass => station.PassFrequency.Hertz > 0
? $"{station.PassFrequency.Kilohertz:0.0} {station.PassCall}".TrimEnd()
: "";
/// Run or search and pounce, which is N1MM's Run column.
public string Run => station.IsRunning ? "Run" : "S&P";
public string Last10 => station.Last10 > 0 ? station.Last10.ToString() : "";
public string Last100 => station.Last100 > 0 ? station.Last100.ToString() : "";
public string Freq => station.Frequency.Hertz > 0 ? $"{station.Frequency.Kilohertz:0.0}" : "";
public string Op => station.Operator;
/// The last message this station sent, and TX while it is transmitting.
/// Two stations keying at once is what an operator watches this column for.
public string Msg => station.IsTransmitting
? $"TX {station.LastMessage}".TrimEnd()
: station.LastMessage;
/// Whether this program can write to the station. N1MM prints OK and so
/// does this.
public string Send => station.IsMine ? "" : station.IsConnected ? "OK" : "no";
/// Whether anything is arriving. A station that has said nothing for a
/// while reads as how long ago that was, because a connection that stands
/// open is not the same as a station that is still there.
public string Receive
{
get
{
if (station.IsMine)
{
return "";
}
TimeSpan since = now - station.LastHeardUtc;
return since < StationLink.Patience ? "OK" : Ago(since);
}
}
public string Band => station.Band?.Name ?? "";
/// Run1 or Run2, which N1MM heads `R/M`.
public string RunMult => station.IsRun2 ? "Run2" : "Run1";
public string Mode => station.Mode?.Name ?? "";
/// The resync columns. N1MM counts blocks of an hour, which is why they
/// say hours.
public string InProgress => station.IsResyncing ? "yes" : "";
public string Status => station.ResyncStatus;
public string HoursOk => station.HoursOk > 0 ? station.HoursOk.ToString() : "";
public string HoursFixed => station.HoursFixed > 0 ? station.HoursFixed.ToString() : "";
public string HoursFailed => station.HoursFailed > 0 ? station.HoursFailed.ToString() : "";
/// Red for a station that is not talking, and for the version mismatch that
/// leaves everything else looking right. Every other row takes the theme's
/// own text colour: the grid reuses its rows, so a row that was red has to
/// be painted back.
public IBrush Colour => !station.IsMine && (station.Refused.Length > 0 || !station.IsConnected)
? Themes.Brush(Themes.Current.BadBackground)
: Themes.Brush(Themes.Current.FieldForeground);
/// How long ago, in the shortest form that says it.
private static string Ago(TimeSpan since) => since switch
{
{ TotalMinutes: < 1 } => $"{since.Seconds} s",
{ TotalHours: < 1 } => $"{since.Minutes} min",
_ => "over an hour",
};
}