/*
*
*
* Adif.cs
*
* ADIF interface.
*
* License: GNU General Public License Version 3.0.
*
* Copyright (C) 2017-2023 by Matthew K. Roberts, KK5JY.
* All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see: http://www.gnu.org/licenses/
*
*
*/
using System;
using System.IO;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace KK5JY.Log {
///
/// ADIF record structure.
///
public class AdifRecord {
#region Public Properties
///
/// The other call.
///
public string Call { get; set; }
///
/// The QSO mode string.
///
public string Mode { get; set; }
///
/// The QSO frequency in MHz (when split, report TX frequency).
///
public string Freq { get; set; }
///
/// The QSO frequency in MHz (for split RX frequency).
///
public string FreqRx { get; set; }
///
/// The QSO date (YYYYMMDD).
///
public string DateOn { get; set; }
///
/// The QSO end date (YYYYMMDD).
///
public string DateOff { get; set; }
///
/// The QSO start time (HHMMSS).
///
public string TimeOn { get; set; }
///
/// The QSO stop time (HHMMSS).
///
public string TimeOff { get; set; }
///
/// Received grid square.
///
public string GridSquare { get; set; }
///
/// Remote station latitude.
///
public string Latitude { get; set; }
///
/// Remote station longitude.
///
public string Longitude { get; set; }
///
/// Sent grid square.
///
public string MyGridSquare { get; set; }
///
/// Received report.
///
public string RecdRST { get; set; }
///
/// Sent report.
///
public string SentRST { get; set; }
///
/// Received exchange.
///
public string RxString { get; set; }
///
/// Sent exchange.
///
public string TxString { get; set; }
///
/// Notes.
///
public string Notes { get; set; }
///
/// Operator (i.e., "my call")
///
public string Operator { get; set; }
#endregion
#region Public Methods
///
/// Return TimeOn as a DateTime.
///
public DateTime TimeOnToDateTime() {
if (String.IsNullOrEmpty(DateOn) ||
String.IsNullOrEmpty(TimeOn)) {
throw new NullReferenceException("DateOn and TimeOn must be non-null and non-empty");
}
return new DateTime(
Int32.Parse(DateOn.Substring(0, 4)),
Int32.Parse(DateOn.Substring(4, 2)),
Int32.Parse(DateOn.Substring(6, 2)),
Int32.Parse(TimeOn.Substring(0, 2)),
Int32.Parse(TimeOn.Substring(2, 2)),
Int32.Parse(TimeOn.Substring(4, 2)),
DateTimeKind.Utc);
}
///
/// Return TimeOff as a DateTime.
///
public DateTime TimeOffToDateTime() {
if (String.IsNullOrEmpty(DateOff) ||
String.IsNullOrEmpty(TimeOff)) {
throw new NullReferenceException("DateOff and TimeOff must be non-null and non-empty");
}
return new DateTime(
Int32.Parse(DateOff.Substring(0, 4)),
Int32.Parse(DateOff.Substring(4, 2)),
Int32.Parse(DateOff.Substring(6, 2)),
Int32.Parse(TimeOff.Substring(0, 2)),
Int32.Parse(TimeOff.Substring(2, 2)),
Int32.Parse(TimeOff.Substring(4, 2)),
DateTimeKind.Utc);
}
///
/// Set the time-on, time-off, and date properties to the specified DateTime.
///
public void SetQsoTime(DateTime timeOff, DateTime ? timeOn = null) {
// if time-on is null, use time-off
if (timeOn == null)
timeOn = timeOff;
// use Z time
if (timeOff.Kind == DateTimeKind.Local)
timeOff = timeOff.ToUniversalTime();
if (timeOn.Value.Kind == DateTimeKind.Local)
timeOn = timeOn.Value.ToUniversalTime();
DateOff = String.Format("{0:0000}{1:00}{2:00}",
timeOff.Year,
timeOff.Month,
timeOff.Day
);
TimeOff = String.Format("{0:00}{1:00}{2:00}",
timeOff.Hour,
timeOff.Minute,
timeOff.Second
);
DateOn = String.Format("{0:0000}{1:00}{2:00}",
timeOn.Value.Year,
timeOn.Value.Month,
timeOn.Value.Day
);
TimeOn = String.Format("{0:00}{1:00}{2:00}",
timeOn.Value.Hour,
timeOn.Value.Minute,
timeOn.Value.Second
);
}
///
/// Parsing states.
///
private enum ParseStates {
Searching,
Tagging,
Measuring,
Reading
}
///
/// Assign an ADIF KVP.
///
private void Assign(string tagName, string newValue) {
// DEBUG
Debug.WriteLine(String.Format("DEBUG: AdifRecord.Assign ({0}, {1})", tagName, newValue));
// write the value to the proper field
switch (tagName) {
case "CALL": {
Call = newValue;
} break;
case "FREQ": {
Freq = newValue;
} break;
case "FREQ_RX": {
FreqRx = newValue;
} break;
case "MODE": {
Mode = newValue;
} break;
case "QSO_DATE": {
DateOn = newValue;
} break;
case "QSO_DATE_OFF": {
DateOff = newValue;
} break;
case "TIME_ON": {
TimeOn = newValue;
} break;
case "TIME_OFF": {
TimeOff = newValue;
} break;
case "GRIDSQUARE": {
GridSquare = newValue;
} break;
case "MY_GRIDSQUARE": {
MyGridSquare = newValue;
} break;
case "LAT": {
Latitude = newValue;
} break;
case "LON": {
Longitude = newValue;
} break;
case "NOTES": {
Notes = newValue;
} break;
case "RST_RECD": {
RecdRST = newValue;
} break;
case "RST_SENT": {
SentRST = newValue;
} break;
case "SRX_STRING": {
RxString = newValue;
} break;
case "STX_STRING": {
TxString = newValue;
} break;
case "OPERATOR": {
Operator = newValue;
} break;
}
}
///
/// Return this object as an ADIF string.
///
public override string ToString() {
StringWriter sw = new StringWriter();
if (!String.IsNullOrEmpty(Call))
sw.Write("{1}", Call.Length, Call);
if (!String.IsNullOrEmpty(Freq))
sw.Write("{1}", Freq.Length, Freq);
if (!String.IsNullOrEmpty(FreqRx))
sw.Write("{1}", FreqRx.Length, FreqRx);
if (!String.IsNullOrEmpty(Mode))
sw.Write("{1}", Mode.Length, Mode);
if (!String.IsNullOrEmpty(DateOn))
sw.Write("{1}", DateOn.Length, DateOn);
if (!String.IsNullOrEmpty(DateOff))
sw.Write("{1}", DateOff.Length, DateOff);
if (!String.IsNullOrEmpty(TimeOn))
sw.Write("{1}", TimeOn.Length, TimeOn);
if (!String.IsNullOrEmpty(TimeOff))
sw.Write("{1}", TimeOff.Length, TimeOff);
if (!String.IsNullOrEmpty(RecdRST))
sw.Write("{1}", RecdRST.Length, RecdRST);
if (!String.IsNullOrEmpty(SentRST))
sw.Write("{1}", SentRST.Length, SentRST);
if (!String.IsNullOrEmpty(RxString))
sw.Write("{1}", RxString.Length, RxString);
if (!String.IsNullOrEmpty(TxString))
sw.Write("{1}", TxString.Length, TxString);
int band = GetBand();
if (band != 0) {
string s = band.ToString();
sw.Write("{1}m", s.Length + 1, s);
}
if (!String.IsNullOrEmpty(GridSquare))
sw.Write("{1}", GridSquare.Length, GridSquare);
if (!String.IsNullOrEmpty(MyGridSquare))
sw.Write("{1}", MyGridSquare.Length, MyGridSquare);
if (!String.IsNullOrEmpty(Latitude))
sw.Write("{1}", Latitude.Length, Latitude);
if (!String.IsNullOrEmpty(Longitude))
sw.Write("{1}", Longitude.Length, Longitude);
if (!String.IsNullOrEmpty(Operator))
sw.Write("{1}", Operator.Length, Operator);
if (!String.IsNullOrEmpty(Notes))
sw.Write("{1}", Notes.Length, Notes);
sw.Write("");
return sw.ToString();
}
///
/// Operator ==
///
public static bool operator==(AdifRecord one, AdifRecord other) {
if (ReferenceEquals(one, null)) return (ReferenceEquals(other, null));
return one.Equals(other);
}
///
/// Operator !=
///
public static bool operator !=(AdifRecord one, AdifRecord other) {
if (ReferenceEquals(one, null)) return (!ReferenceEquals(other, null));
return !one.Equals(other);
}
///
/// Equality testing.
///
public override bool Equals(object obj) {
var adif = obj as AdifRecord;
if (ReferenceEquals(adif, null)) return false;
return
String.Equals(Call, adif.Call, StringComparison.InvariantCultureIgnoreCase) &&
String.Equals(Mode, adif.Mode, StringComparison.InvariantCultureIgnoreCase) &&
String.Equals(Freq, adif.Freq, StringComparison.InvariantCultureIgnoreCase) &&
String.Equals(FreqRx, adif.FreqRx, StringComparison.InvariantCultureIgnoreCase) &&
String.Equals(GridSquare, adif.GridSquare, StringComparison.InvariantCultureIgnoreCase) &&
String.Equals(Latitude, adif.Latitude, StringComparison.InvariantCultureIgnoreCase) &&
String.Equals(Longitude, adif.Longitude, StringComparison.InvariantCultureIgnoreCase) &&
String.Equals(DateOn, adif.DateOn, StringComparison.InvariantCultureIgnoreCase) &&
String.Equals(DateOff, adif.DateOff, StringComparison.InvariantCultureIgnoreCase) &&
String.Equals(TimeOn, adif.TimeOn, StringComparison.InvariantCultureIgnoreCase) &&
String.Equals(TimeOff, adif.TimeOff, StringComparison.InvariantCultureIgnoreCase) &&
String.Equals(RecdRST, adif.RecdRST, StringComparison.InvariantCultureIgnoreCase) &&
String.Equals(SentRST, adif.SentRST, StringComparison.InvariantCultureIgnoreCase) &&
String.Equals(RxString, adif.RxString, StringComparison.InvariantCultureIgnoreCase) &&
String.Equals(TxString, adif.TxString, StringComparison.InvariantCultureIgnoreCase) &&
String.Equals(Operator, adif.Operator, StringComparison.InvariantCultureIgnoreCase);
}
///
/// Return the hash code of the ADIF string.
///
public override int GetHashCode() {
return ToString().GetHashCode();
}
///
/// Parse an ADIF string.
///
public static AdifRecord Parse(string s) {
if (String.IsNullOrEmpty(s))
return null;
AdifRecord result = new AdifRecord();
ParseStates state = ParseStates.Searching;
string working = null;
string tagName = null;
int length = 0;
for (int i = 0; i != s.Length; ++i) {
char ch = s[i];
switch (state) {
case ParseStates.Searching: {
if (ch == '<') {
state = ParseStates.Tagging;
working = "";
tagName = "";
length = 0;
}
} break;
case ParseStates.Tagging: {
if (ch == ':') {
state = ParseStates.Measuring;
tagName = working;
working = "";
} else {
working += ch;
}
} break;
case ParseStates.Measuring: {
if (ch == '>') {
state = ParseStates.Reading;
if (!Int32.TryParse(working, out length)) {
length = -1;
}
working = "";
} else {
working += ch;
}
} break;
case ParseStates.Reading: {
if (ch == '<') {
if (working.Length != length) {
throw new ArgumentException(String.Format("Tag length does not match data: {0}: {1} != {2}", tagName, length, working.Length));
}
if (!String.IsNullOrEmpty(working)) {
result.Assign(tagName, working);
}
state = ParseStates.Tagging;
working = "";
tagName = "";
length = 0;
} else {
working += ch;
}
} break;
}
}
if (!String.IsNullOrEmpty(tagName)) {
if (working.Length != length) {
throw new ArgumentException(String.Format("Tag length does not match data: {0}: {1} != {2}", tagName, length, working.Length));
}
result.Assign(tagName, working);
}
return result;
}
///
/// Read a single record from a file.
///
public static AdifRecord ReadFromFile(FileStream file) {
int b;
int state = 0;
string key = "";
AdifRecord result = new AdifRecord();
while ((b = file.ReadByte()) != -1) {
char ch = Convert.ToChar(b);
switch (state) {
case 0: {
if (ch == '<') {
state = 1;
}
} break;
case 1: {
if (ch == '>') {
var parts = key.Split(':');
if (String.Equals(key, "eor", StringComparison.InvariantCultureIgnoreCase))
return result;
if (parts.Length < 2)
return null;
key = parts[0];
int length = Int32.Parse(parts[1]);
string value = "";
for (int i = 0; i != length; ++i) {
value += (char)(file.ReadByte());
}
switch (key.ToUpper()) {
case "CALL": {
result.Call = value;
} break;
case "MODE": {
result.Mode = value;
} break;
case "FREQ": {
result.Freq = value;
} break;
case "FREQ_RX": {
result.FreqRx = value;
} break;
case "QSO_DATE": {
result.DateOn = value;
} break;
case "QSO_DATE_OFF": {
result.DateOff = value;
} break;
case "TIME_ON": {
result.TimeOn = value;
} break;
case "TIME_OFF": {
result.TimeOff = value;
} break;
case "RST_RECD": {
result.RecdRST = value;
} break;
case "RST_SENT": {
result.SentRST = value;
} break;
case "SRX_STRING": {
result.RxString = value;
} break;
case "STX_STRING": {
result.TxString = value;
} break;
case "GRIDSQUARE": {
result.GridSquare = value;
} break;
case "MY_GRIDSQUARE": {
result.MyGridSquare = value;
} break;
case "LAT": {
result.Latitude = value;
} break;
case "LON": {
result.Longitude = value;
} break;
case "OPERATOR": {
result.Operator = value;
} break;
case "NOTES": {
result.Notes = value;
} break;
case "BAND": {
// nop
} break;
}
state = 0;
key = "";
break;
}
key += ch;
} break;
}
}
return null;
}
///
/// Convert the frequency field into a band.
///
public int GetBand() {
decimal freq = 0;
if (Decimal.TryParse(Freq, out freq)) {
return GetBand(freq);
}
return 0;
}
///
/// Convert a frequency (in MHz) into a band (in m).
///
public static int GetBand(decimal freq) {
switch ((int)(freq)) {
case 1: return 160;
case 3: return 80;
case 5: return 60;
case 7: return 40;
case 10: return 30;
case 14: return 20;
case 18: return 17;
case 21: return 15;
case 24: return 12;
case 28:
case 29: return 10;
case 50:
case 51:
case 52:
case 53: return 6;
case 144:
case 145:
case 146:
case 147: return 2;
}
return 0;
}
#endregion
}
///
/// ADIF header block.
///
public class AdifHeader {
#region Public Properties
///
/// Lead-in text.
///
public string LeadIn { get; set; }
///
/// Header fields.
///
public Dictionary Fields { get; private set; }
#endregion
#region Constructors
public AdifHeader() {
Clear();
}
#endregion
#region Public Methods
///
/// Clear all fields.
///
public void Clear() {
LeadIn = "";
Fields = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
}
///
/// Read header from file.
///
public void ReadFromFile(FileStream f) {
if (f.CanSeek) {
f.Seek(0, SeekOrigin.Begin);
}
int b = 0;
int state = 0;
string key = "";
while ((b = f.ReadByte()) != -1) {
char ch = (char)(b);
switch (state) {
// looking for an opening bracket, recording lead-in text
case 0: {
if (ch == '<') {
state = 2;
break;
}
LeadIn += ch;
} break;
// looking for the next opening bracket
case 1: {
if (ch == '<') {
state = 2;
}
} break;
// recording the tag
case 2: {
// if the tag is closed, process the tag and its value
if (ch == '>') {
var parts = key.Split(':');
if (String.Equals(key, "eoh", StringComparison.InvariantCultureIgnoreCase))
return;
key = parts[0];
int length = Int32.Parse(parts[1]);
string value = "";
for (int i = 0; i != length; ++i) {
value += (char)(f.ReadByte());
}
Fields[key] = value;
state = 1;
key = "";
break;
}
key += ch;
} break;
}
}
}
///
/// Write the record to a file.
///
public void WriteToFile(FileStream f) {
using (var sw = new StreamWriter(f, Encoding.ASCII)) {
if (String.IsNullOrEmpty(LeadIn)) {
sw.WriteLine("KK5JY ADIF for C#");
} else {
sw.Write(LeadIn);
}
// write
string adif_version = "3.0.5";
if (Fields.ContainsKey("adif_ver")) {
adif_version = Fields["adif_ver"];
}
sw.WriteLine("<{0}:{1}>{2}", "adif_ver", adif_version.Length, adif_version);
// write all the others
foreach (var kvp in Fields) {
if (kvp.Key.Equals("adif_ver", StringComparison.InvariantCultureIgnoreCase)) {
continue;
}
sw.WriteLine("<{0}:{1}>{2}", kvp.Key, kvp.Value.Length, kvp.Value);
}
sw.WriteLine("");
}
}
#endregion
}
///
/// Basic interface to ADIF file.
///
public interface IAdifFile {
///
/// Read the ADIF header.
///
AdifHeader ReadHeader();
///
/// Add a single record.
///
void AddRecord(AdifRecord rec);
///
/// Fetch all records in the file.
///
IEnumerable GetAllRecords();
///
/// Fetch records from the file matching the search criteria.
///
IEnumerable GetRecords(string call = null, int? bandInMeters = null, string mode = null);
///
/// Test the log for a match to the provided search terms.
///
bool GetMatch(string call = null, int? bandInMeters = null, string mode = null);
///
/// Delete first matching record from ADIF.
///
bool DeleteRecord(AdifRecord needle);
}
///
/// ADIF file.
///
public class AdifFile : IAdifFile {
#region Public Properties
// the read cache (optional)
private LinkedList m_ReadCache;
private object m_ReadLock;
private long m_CacheSize;
#endregion
#region Public Properties
///
/// Path to the ADIF file.
///
public string Path { get; private set; }
///
/// Return the number of records in the file.
///
public int Count {
get {
try {
// if cache enabled...
if (ReadCacheEnabled) {
lock (m_ReadLock) {
// invalidate the cache if the file size has changed
var fi = new FileInfo(Path);
if (fi.Length != m_CacheSize) {
m_ReadCache = null;
}
// use the cached value if available
if (! ReferenceEquals(m_ReadCache, null)) {
return m_ReadCache.Count;
}
}
}
// as a last resort, read the file and count entries
return GetAllRecords().Count();
} catch {
return -1;
}
}
}
///
/// Return true iff read caching was enabled at construction.
///
public bool ReadCacheEnabled { get; private set; }
#endregion
#region Constructors
///
/// Access to ADIF file.
///
public AdifFile(string path, bool enableReadCache = false) {
// make the lock object
m_ReadLock = new object();
// sanity checks
if (path == null)
throw new ArgumentNullException("path");
if (String.IsNullOrEmpty(path))
throw new ArgumentException("'path' argument cannot be empty");
// keep a copy of the path and cache flag
Path = path;
ReadCacheEnabled = enableReadCache;
// create the directory if it doesn't exist
var dir = System.IO.Path.GetDirectoryName(path);
if (!String.IsNullOrEmpty(dir) && !Directory.Exists(dir)) {
Directory.CreateDirectory(dir);
}
// create the file if it doesn't exist
if (!File.Exists(path)) {
using (var file = File.OpenWrite(path)) {
var header = new AdifHeader();
header.WriteToFile(file);
}
}
// update the cache size
if (ReadCacheEnabled) {
var fi = new FileInfo(path);
m_CacheSize = fi.Length;
}
}
#endregion
#region Public Methods
///
/// Read the ADIF header.
///
public AdifHeader ReadHeader() {
using (var file = File.Open(Path, FileMode.Open, FileAccess.Read, FileShare.None)) {
var result = new AdifHeader();
result.ReadFromFile(file);
return result;
}
}
///
/// Add a single record.
///
public void AddRecord(AdifRecord rec) {
if (ReferenceEquals(rec, null)) {
throw new ArgumentNullException("rec", "The ADIF record cannot be null");
}
// read the file length
long len = 0;
try {
var fi = new FileInfo(Path);
len = fi.Length;
} catch (FileNotFoundException) {
len = 0;
}
// if it is zero, or if the file doesn't exist, write out a header
if (len == 0) {
using (var file = File.Open(Path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None)) {
AdifHeader h = new AdifHeader(); // default header
h.WriteToFile(file);
}
}
// open the file and write the record at the END
using (var file = File.Open(Path, FileMode.Open, FileAccess.Write, FileShare.None)) {
file.Seek(0, SeekOrigin.End);
using (var sw = new StreamWriter(file, Encoding.ASCII)) {
sw.WriteLine(rec.ToString());
}
}
// update the cache; the cache can be null even if
// enabled; it will be rebuilt on first read
if (ReadCacheEnabled && !ReferenceEquals(m_ReadCache, null)) {
m_ReadCache.AddLast(rec);
// update the cache size
var fi = new FileInfo(Path);
m_CacheSize = fi.Length;
}
}
///
/// Fetch all records in the file.
///
public IEnumerable GetAllRecords() {
List cacheResult = null;
lock (m_ReadLock) {
if (ReadCacheEnabled) {
// invalidate the cache if the file size has changed
var fi = new FileInfo(Path);
if (fi.Length != m_CacheSize) {
m_ReadCache = null;
}
// if the cache is invalid, reload it
if (ReferenceEquals(m_ReadCache, null)) {
m_ReadCache = new LinkedList();
if (File.Exists(Path)) {
// save the size
fi = new FileInfo(Path);
m_CacheSize = fi.Length;
// read the file contents
using (var file = File.Open(Path, FileMode.Open, FileAccess.Read, FileShare.None)) {
AdifHeader h = new AdifHeader();
h.ReadFromFile(file);
do {
AdifRecord r = AdifRecord.ReadFromFile(file);
if (ReferenceEquals(r, null)) break;
m_ReadCache.AddLast(r);
} while (true);
}
}
}
// built a temp result list
cacheResult = new List(m_ReadCache);
}
}
// if caching is working, return the temp list outside the lock
if ( ! ReferenceEquals(cacheResult, null)) {
foreach (var item in cacheResult)
yield return item;
yield break;
}
// otherwise read records directly from the file
if (File.Exists(Path)) {
using (var file = File.Open(Path, FileMode.Open, FileAccess.Read, FileShare.None)) {
AdifHeader h = new AdifHeader();
/*
if (ReadCacheEnabled) {
m_ReadCache = new LinkedList();
}
*/
h.ReadFromFile(file);
do {
AdifRecord r = AdifRecord.ReadFromFile(file);
if (ReferenceEquals(r, null)) break;
yield return r;
} while (true);
}
}
}
///
/// Search for a match to the provided terms.
///
public bool GetMatch(string call = null, int? bandInMeters = null, string mode = null) {
if ( ! ReferenceEquals(call, null)) {
call = call.ToUpper();
}
if ( ! ReferenceEquals(mode, null)) {
mode = mode.ToUpper();
}
foreach (var record in GetAllRecords()) {
// match against call
if (!String.IsNullOrEmpty(call))
if (!String.Equals(call, record.Call))
continue;
// match against band
if (bandInMeters != null) {
var recBand = record.GetBand();
if (bandInMeters != recBand)
continue;
}
// match against mode
if (!String.IsNullOrEmpty(mode))
if (!String.Equals(mode, record.Mode))
continue;
// if we made it this far, the record matches
return true;
}
return false;
}
///
/// Fetch records from the file matching the search criteria.
///
public IEnumerable GetRecords(string call = null, int? bandInMeters = null, string mode = null) {
if ( ! ReferenceEquals(call, null)) {
call = call.ToUpper();
}
if ( ! ReferenceEquals(mode, null)) {
mode = mode.ToUpper();
}
foreach (var record in GetAllRecords()) {
// match against call
if (!String.IsNullOrEmpty(call))
if (!String.Equals(call, record.Call))
continue;
// match against band
if (bandInMeters != null) {
var recBand = record.GetBand();
if (bandInMeters != recBand)
continue;
}
// match against mode
if (!String.IsNullOrEmpty(mode))
if (!String.Equals(mode, record.Mode))
continue;
// if we made it this far, the record matches
yield return record;
}
}
///
/// Delete first matching record from ADIF.
///
public bool DeleteRecord(AdifRecord needle) {
if (ReferenceEquals(needle, null)) {
throw new ArgumentNullException("needle", "The ADIF record cannot be null");
}
// build a replacement file, with the requested record removed
var tmp = new AdifFile(Path + ".tmp");
bool found = false;
long count = 0;
foreach (var rec in GetAllRecords()) {
if (rec == needle) {
if (!found) {
found = true;
continue;
}
}
tmp.AddRecord(rec);
++count;
}
// if that results in no records, just write a temp file with a header like the old one
if (count == 0) {
using (var file = File.Create(tmp.Path)) {
ReadHeader().WriteToFile(file);
}
}
// invalidate the cache
lock (m_ReadLock)
m_ReadCache = null;
// and move the temp file to the original path
File.Replace(tmp.Path, Path, null);
// return true if work was done
return found;
}
#endregion
}
} // namespace KK5JY.Log
// EOF