ADIF record repairs

* Add code to allow on the fly correction of corrupt
    ADIF records during file read.
pull/4/head
David Freese 2015-11-12 06:25:32 -06:00
rodzic 93342e4dd7
commit 2e2c9e90f9
4 zmienionych plików z 116 dodań i 82 usunięć

Wyświetl plik

@ -32,6 +32,7 @@ using namespace std;
struct FIELD {
int type;
int fsize;
const char *name;
Fl_Check_Button **btn;
};

Wyświetl plik

@ -33,7 +33,7 @@ private:
bool write_all;
cQsoRec *adifqso;
FILE *adiFile;
char *fillfield(int, char *);
char *fillfield(int, int, char *);
static int instances;
public:
cAdifIO ();

Wyświetl plik

@ -54,70 +54,78 @@ static const char *szEOR = "<EOR>";
// These ADIF fields define the ADIF database
FIELD fields[] = {
// TYPE, NAME, WIDGET
{FREQ, "FREQ", &btnSelectFreq}, // QSO frequency in Mhz
{CALL, "CALL", &btnSelectCall}, // contacted stations CALLSIGN
{MODE, "MODE", &btnSelectMode}, // QSO mode
{NAME, "NAME", &btnSelectName}, // contacted operators NAME
{QSO_DATE, "QSO_DATE", &btnSelectQSOdateOn}, // QSO data
{QSO_DATE_OFF, "QSO_DATE_OFF", &btnSelectQSOdateOff},// QSO data OFF, according to ADIF 2.2.6
{TIME_OFF, "TIME_OFF", &btnSelectTimeOFF}, // HHMM or HHMMSS in UTC
{TIME_ON, "TIME_ON", &btnSelectTimeON}, // HHMM or HHMMSS in UTC
{QTH, "QTH", &btnSelectQth}, // contacted stations city
{RST_RCVD, "RST_RCVD", &btnSelectRSTrcvd}, // received signal report
{RST_SENT, "RST_SENT", &btnSelectRSTsent}, // sent signal report
{STATE, "STATE", &btnSelectState}, // contacted stations STATE
{VE_PROV, "VE_PROV", &btnSelectProvince}, // 2 letter abbreviation for Canadian Province
{NOTES, "NOTES", &btnSelectNotes}, // QSO notes
{QSLRDATE, "QSLRDATE", &btnSelectQSLrcvd}, // QSL received date
{QSLSDATE, "QSLSDATE", &btnSelectQSLsent}, // QSL sent date
{GRIDSQUARE, "GRIDSQUARE", &btnSelectLOC}, // contacted stations Maidenhead Grid Square
{BAND, "BAND", &btnSelectBand}, // QSO band
{CNTY, "CNTY", &btnSelectCNTY}, // secondary political subdivision, ie: county
{COUNTRY, "COUNTRY", &btnSelectCountry}, // contacted stations DXCC entity name
{CQZ, "CQZ", &btnSelectCQZ}, // contacted stations CQ Zone
{DXCC, "DXCC", &btnSelectDXCC}, // contacted stations Country Code
{QSL_VIA, "QSL_VIA", &btnSelectQSL_VIA}, // contacted stations path
{IOTA, "IOTA", &btnSelectIOTA}, // Islands on the air
{ITUZ, "ITUZ", &btnSelectITUZ}, // ITU zone
{CONT, "CONT", &btnSelectCONT}, // contacted stations continent
// TYPE, FSIZE, NAME, WIDGET
{FREQ, 12, "FREQ", &btnSelectFreq}, // QSO frequency in Mhz
{CALL, 15, "CALL", &btnSelectCall}, // contacted stations CALLSIGN
{MODE, 10, "MODE", &btnSelectMode}, // QSO mode
{NAME, 80, "NAME", &btnSelectName}, // contacted operators NAME
{QSO_DATE, 8, "QSO_DATE", &btnSelectQSOdateOn}, // QSO data
{QSO_DATE_OFF, 8, "QSO_DATE_OFF", &btnSelectQSOdateOff},// QSO data OFF, according to ADIF 2.2.6
{TIME_OFF, 6, "TIME_OFF", &btnSelectTimeOFF}, // HHMM or HHMMSS in UTC
{TIME_ON, 6, "TIME_ON", &btnSelectTimeON}, // HHMM or HHMMSS in UTC
{QTH, 100, "QTH", &btnSelectQth}, // contacted stations city
{RST_RCVD, 3, "RST_RCVD", &btnSelectRSTrcvd}, // received signal report
{RST_SENT, 3, "RST_SENT", &btnSelectRSTsent}, // sent signal report
{STATE, 4, "STATE", &btnSelectState}, // contacted stations STATE
{VE_PROV, 4, "VE_PROV", &btnSelectProvince}, // 2 letter abbreviation for Canadian Province
{NOTES, 256, "NOTES", &btnSelectNotes}, // QSO notes
{QSLRDATE, 8, "QSLRDATE", &btnSelectQSLrcvd}, // QSL received date
{QSLSDATE, 8, "QSLSDATE", &btnSelectQSLsent}, // QSL sent date
{GRIDSQUARE, 8, "GRIDSQUARE", &btnSelectLOC}, // contacted stations Maidenhead Grid Square
{BAND, 8, "BAND", &btnSelectBand}, // QSO band
{CNTY, 20, "CNTY", &btnSelectCNTY}, // secondary political subdivision, ie: county
{COUNTRY, 40, "COUNTRY", &btnSelectCountry}, // contacted stations DXCC entity name
{CQZ, 8, "CQZ", &btnSelectCQZ}, // contacted stations CQ Zone
{DXCC, 8, "DXCC", &btnSelectDXCC}, // contacted stations Country Code
{QSL_VIA, 50, "QSL_VIA", &btnSelectQSL_VIA}, // contacted stations path
{IOTA, 20, "IOTA", &btnSelectIOTA}, // Islands on the air
{ITUZ, 20, "ITUZ", &btnSelectITUZ}, // ITU zone
{CONT, 20, "CONT", &btnSelectCONT}, // contacted stations continent
{SRX, "SRX", &btnSelectSerialIN}, // received serial number for a contest QSO
{STX, "STX", &btnSelectSerialOUT}, // QSO transmitted serial number
{SRX, 50, "SRX", &btnSelectSerialIN}, // received serial number for a contest QSO
{STX, 50, "STX", &btnSelectSerialOUT}, // QSO transmitted serial number
{XCHG1, "SRX_STRING", &btnSelectXchgIn}, // contest exchange #1 / free1 in xlog
{MYXCHG, "STX_STRING", &btnSelectMyXchg}, // contest exchange sent
{XCHG1, 100, "SRX_STRING", &btnSelectXchgIn}, // contest exchange #1 / free1 in xlog
{MYXCHG, 100, "STX_STRING", &btnSelectMyXchg}, // contest exchange sent
{TX_PWR, "TX_PWR", &btnSelectTX_pwr}, // power transmitted by this station
{NUMFIELDS, "", NULL}
{TX_PWR, 8, "TX_PWR", &btnSelectTX_pwr}, // power transmitted by this station
{NUMFIELDS, 0, "", NULL}
};
// This ADIF fields is in the fldigi QSO database, but not saved in the ADIF file
/*
{EXPORT, "EXPORT", NULL}, // used to indicate record is to be exported
{EXPORT, 0, "EXPORT", NULL}, // used to indicate record is to be exported
*/
// These ADIF fields are not in the fldigi QSO database
/*
{COMMENT, "COMMENT", NULL}, // comment field for QSO
{ADDRESS, "ADDRESS", NULL}, // contacted stations mailing address
{AGE, "AGE", NULL}, // contacted operators age in years
{ARRL_SECT, "ARRL_SECT", NULL}, // contacted stations ARRL section
{CONTEST_ID, "CONTEST_ID", NULL}, // QSO contest identifier
{OPERATOR, "OPERATOR", NULL}, // Callsign of person logging the QSO
{PFX, "PFX", NULL}, // WPA prefix
{PROP_MODE, "PROP_MODE", NULL}, // propogation mode
{QSL_MSG, "QSL_MSG", NULL}, // personal message to appear on qsl card
{QSL_RCVD, "QSL_RCVD", NULL}, // QSL received status
{QSL_SENT, "QSL_SENT", NULL}, // QSL sent status
{QSL_VIA, "QSL_VIA", NULL}, // QSL via this person
{RX_PWR, "RX_PWR", NULL}, // power of other station in watts
{SAT_MODE, "SAT_MODE", NULL}, // satellite mode
{SAT_NAME, "SAT_NAME", NULL}, // satellite name
{TEN_TEN, "TEN_TEN", NULL} // ten ten # of other station
{COMMENT, 256, "COMMENT", NULL}, // comment field for QSO
{ADDRESS, 256, "ADDRESS", NULL}, // contacted stations mailing address
{AGE, 2, "AGE", NULL}, // contacted operators age in years
{ARRL_SECT, 20, "ARRL_SECT", NULL}, // contacted stations ARRL section
{CONTEST_ID, 20, "CONTEST_ID", NULL}, // QSO contest identifier
{OPERATOR, 20, "OPERATOR", NULL}, // Callsign of person logging the QSO
{PFX, 20, "PFX", NULL}, // WPA prefix
{PROP_MODE, 100, "PROP_MODE", NULL}, // propogation mode
{QSL_MSG, 256, "QSL_MSG", NULL}, // personal message to appear on qsl card
{QSL_RCVD, 4, "QSL_RCVD", NULL}, // QSL received status
{QSL_SENT, 4, "QSL_SENT", NULL}, // QSL sent status
{QSL_VIA, 20, "QSL_VIA", NULL}, // QSL via this person
{RX_PWR, 8, "RX_PWR", NULL}, // power of other station in watts
{SAT_MODE, 20, "SAT_MODE", NULL}, // satellite mode
{SAT_NAME, 20, "SAT_NAME", NULL}, // satellite name
{TEN_TEN, 10, "TEN_TEN", NULL} // ten ten # of other station
};
*/
static string read_errors;
static int num_read_errors;
static void write_rxtext(const char *s)
{
ReceiveText->addstr(s);
}
static char *fastlookup = 0;
static unsigned int maxlen = 0;
@ -181,7 +189,7 @@ cAdifIO::~cAdifIO()
}
}
char * cAdifIO::fillfield (int fieldnum, char *buff)
char * cAdifIO::fillfield (int recnbr, int fieldnum, char *buff)
{
char *p1 = strchr(buff, ':');
char *p2 = strchr(buff, '>');
@ -197,20 +205,30 @@ char * cAdifIO::fillfield (int fieldnum, char *buff)
}
p1++;
}
string tmp = "";
tmp.assign(p2+1, fldsize);
if ((fieldnum == TIME_ON || fieldnum == TIME_OFF) && fldsize < 6) {
while (tmp.length() < 6) tmp += '0';
adifqso->putField(fieldnum, tmp.c_str(), 6);
} else {
adifqso->putField (fieldnum, p2+1, fldsize);
}
return p2 + fldsize + 1;
}
static void write_rxtext(const char *s)
{
ReceiveText->addstr(s);
// added to disallow very large corrupted adif fields
if (fldsize > fields[fieldnum].fsize) {
string bfr = buff;
tmp.erase(fields[fieldnum].fsize);
static char szmsg[1000];
snprintf(szmsg, sizeof(szmsg),
"In record # %d, <%s, too large, saving first %d characters\n",
recnbr+1,
bfr.substr(0, (int)(p2+1 - buff)).c_str(),
fields[fieldnum].fsize );
read_errors.append(szmsg);
num_read_errors++;
}
if ((fieldnum == TIME_ON || fieldnum == TIME_OFF) && fldsize < 6)
while (tmp.length() < 6) tmp += '0';
adifqso->putField( fieldnum, tmp.c_str(), tmp.length() );
return p2 + fldsize + 1;
}
void cAdifIO::do_readfile(const char *fname, cQsoDb *db)
@ -218,6 +236,9 @@ void cAdifIO::do_readfile(const char *fname, cQsoDb *db)
int found;
static char szmsg[200];
read_errors.clear();
num_read_errors = 0;
// open the adif file
FILE *adiFile = fopen (fname, "rb");
@ -233,13 +254,13 @@ void cAdifIO::do_readfile(const char *fname, cQsoDb *db)
clock_gettime(CLOCK_REALTIME, &t0);
#endif
char buff[8192];
char buff[16384];
string sbuff;
memset(buff, 0, 8192);
int retnbr = fread(buff, 1, 8192, adiFile);
memset(buff, 0, 16384);
int retnbr = fread(buff, 1, 16384, adiFile);
if (retnbr) sbuff.assign(buff, retnbr);
size_t p = sbuff.find("<EOH>");
size_t p = sbuff.find("<EOH>");
if (p == std::string::npos) p = sbuff.find("<eoh>");
if (p == std::string::npos) {
return;
@ -249,12 +270,18 @@ void cAdifIO::do_readfile(const char *fname, cQsoDb *db)
// skip over cr/lf pairs
while (sbuff.length() && (sbuff[0] == '\r' || sbuff[0] == '\n'))
sbuff.erase(0,1);
retnbr = fread(buff, 1, 8192, adiFile);
if (retnbr) sbuff.append(buff, retnbr);
p = sbuff.find("<EOR>");
if (p == std::string::npos) p = sbuff.find("<eor>");
while (!feof(adiFile) && (p == std::string::npos)) {
retnbr = fread(buff, 1, 16384, adiFile);
if (retnbr) sbuff.append(buff, retnbr);
p = sbuff.find("<EOR>");
if (p == std::string::npos) p = sbuff.find("<eor>");
}
if (p == std::string::npos) return;
char *ptr = 0, *ptr2 = 0;
int recnbr = 0;
@ -267,7 +294,7 @@ void cAdifIO::do_readfile(const char *fname, cQsoDb *db)
found = findfield(ptr+1);
if (found > -1) {
if (!adifqso) adifqso = db->newrec(); // need new record in db
ptr2 = fillfield (found, ptr+1);
ptr2 = fillfield (recnbr, found, ptr+1);
} else if (found == -1) { // <eor> reached;
break;
} else {
@ -286,9 +313,9 @@ void cAdifIO::do_readfile(const char *fname, cQsoDb *db)
p = sbuff.find("<EOR>");
if (p == std::string::npos) p = sbuff.find("<eor>");
if (p == std::string::npos) {
memset(buff, 0, 8192);
retnbr = fread(buff, 1, 8192, adiFile);
while (!feof(adiFile) && (p == std::string::npos)) {
retnbr = fread(buff, 1, 16384, adiFile);
if (retnbr) sbuff.append(buff, retnbr);
p = sbuff.find("<EOR>");
if (p == std::string::npos) p = sbuff.find("<eor>");
@ -308,15 +335,21 @@ void cAdifIO::do_readfile(const char *fname, cQsoDb *db)
snprintf(szmsg, sizeof(szmsg), "\
ERROR reading logbook %s\n\
read %d records in %4.2f seconds", fname, db->nbrRecs(), t);
else
else {
snprintf(szmsg, sizeof(szmsg), "\
Loaded logbook %s\n\
read %d records in %4.2f seconds", fname, db->nbrRecs(), t);
REQ(write_rxtext, "\n");
REQ(write_rxtext, szmsg);
REQ(write_rxtext, "\n");
LOG_INFO("%s", szmsg);
read %d records in %4.2f seconds\n", fname, db->nbrRecs(), t);
LOG_INFO("%s", szmsg);
if (num_read_errors) {
read_errors.insert(0, szmsg);
snprintf(szmsg, sizeof(szmsg),
"Corrected %d errors\nSave logbook and then reload",
num_read_errors);
read_errors.append(szmsg);
REQ(write_rxtext, read_errors.c_str());
} else
REQ(write_rxtext, szmsg);
}
if (db == &qsodb)
REQ(adif_read_OK);

Wyświetl plik

@ -276,7 +276,7 @@ void cb_mnuOpenLogbook(Fl_Menu_* m, void* d)
{
string title = _("Open logbook file");
string filter;
filter.assign("ADIF file\t*.").append(ADIF_SUFFIX);
filter.assign("ADIF file\t*.{adi,adif}");
#ifdef __APPLE__
filter.append("\n");
#endif
@ -503,7 +503,7 @@ void merge_recs( cQsoDb *db, cQsoDb *mrgdb ) // (haystack, needle)
}
void cb_mnuMergeADIF_log(Fl_Menu_* m, void* d) {
const char* p = FSEL::select(_("Merge ADIF file"), "ADIF\t*." ADIF_SUFFIX, LogsDir.c_str());
const char* p = FSEL::select(_("Merge ADIF file"), "ADIF\t*.{adi,adif}", LogsDir.c_str());
Fl::wait();
fl_digi_main->redraw();
Fl::awake();