/* Contest Arbitrator v0.3.2 by Dmitry Gorokh, UR4MCK
This program is FREEWARE!
Changes:
11.03.2015: skiping hidden files and special dirs inside the logs directory
*/
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <unistd.h>
#include <time.h>
#include <dirent.h>
#include "config.h"
#include "main.h"
#include "cabrillo.h"
#include "contests/wakeup.h"
#include "contests/general.h"
#include "report.h"
#define OPTION_LEN (MAX_STR) /* Max option length */
/* Configuration file parameter names */
#define CONFIG_PARAM_NAME "name" /* Contest name */
#define CONFIG_PARAM_ID "id" /* Contest id */
#define CONFIG_PARAM_START "start" /* Contest start date & time */
#define CONFIG_PARAM_END "end" /* Contest end date & time */
#define CONFIG_PARAM_ROUNDS "rounds" /* Number of rounds */
#define CONFIG_PARAM_MAX_DT "max_dt" /* Maximum allowable difference in time */
#define CONFIG_PARAM_BAND_LIST "band_list" /* Band list */
#define CONFIG_PARAM_MODE_LIST "mode_list" /* Mode list */
#define CONFIG_PARAM_SIGNATURE "signature" /* Judge's signature */
#define CONFIG_PARAM_SKIP_RST_SNT "skip_rst_snt" /* Sent RST checking */
#define CONFIG_PARAM_SKIP_RST_RCV "skip_rst_rcv" /* Received RST checking */
#define CONFIG_PARAM_SKIP_NUM_SNT "skip_num_snt" /* Sent incremental number checking */
#define CONFIG_PARAM_SKIP_NUM_RCV "skip_num_rcv" /* Received incremental number checking */
#define CONFIG_PARAM_SKIP_CHK_SNT "skip_chk_snt" /* Sent check number checking */
#define CONFIG_PARAM_SKIP_CHK_RCV "skip_chk_rcv" /* Received check number checking */
#define CONFIG_PARAM_CFM_UNRESOLVED "cfm_unresolved"/* Minimum number of unresolved QSOs to confirm them as valid */
#define CONFIG_PARAM_REPORT_DIR "report_dir" /* Log analyzis reports directory */
#define CONFIG_PARAM_UBN_DIR "ubn_dir" /* UBN files directory */
#define CONFIG_PARAM_LOG_EMAILS "log_emails" /* Log submissions emails list */
#define CONFIG_PARAM_WWW "www" /* Official results URL */
#define CONFIG_PARAM_UNIQ_MULT "uniq_mult" /* Flag to count a multiplier only once */
#define CONFIG_PARAM_STRICT_EVAL "strict_eval" /* Flag to perform strict evaluation */
#define CONFIG_PARAM_ALLOW_DUPES "allow_dupes" /* Flag to allow duplicate QSOs */
#define CONFIG_PARAM_SEARCH_TAGS "search_tags" /* Flag to search similar tags upon log errors */
#define CONFIG_PARAM_IGNORE_TAGS "ignore_tags" /* Flag to ignore unknown tags */
/* Score table entry */
typedef struct {
HAM_S *ham; /* Pointer to ham record in the database */
unsigned int qso; /* Claimed QSOs */
unsigned int cfm; /* Confirmed QSOs */
} SCORE_S;
CONFIG_S config; /* Configuration structure */
static BASE_S base; /* Participant's data base */
/* TODO: replace with callback funcs */
extern int parse_logs(CONFIG_S *config, BASE_S *base);
extern int free_logs(BASE_S *base);
extern int cross_check(BASE_S *base, int contest_id);
extern int strip_leading_spaces(char *str);
extern int strip_trailing_spaces(char *str);
static void usage(void);
static int get_config(char *file);
static int parse_config(char *name, char *value, char **err_msg);
static LOG_FILE_S *alloc_list(char *name);
static void free_list(LOG_FILE_S *ptr);
static int score_table(char *name, time_t start, time_t end, BASE_S *base);
int main(int argc, char *argv[]) {
/* Program entry */
int opt, n, have_config;
LOG_FILE_S *cur, *prev;
DIR *dir;
time_t t;
struct tm tm;
struct dirent *dir_entry;
char str[MAX_STR], *s;
DEBUG("==> main(%d, %p)\n", argc, argv);
printf("Contest Arbitrator version %s\n"
" (c) 2009-2014 by Dmitry Gorokh, UR4MCK\n"
" This program is FREEWARE\n\n", VERSION);
/* Initialization */
memset(&config, 0, sizeof(config));
memset(&base, 0, sizeof(base));
cabrillo_init();
have_config = 0;
/* Check for supplied options */
while ((opt = getopt(argc, argv, "hc:d:o:r:u:")) != EOF) {
switch (opt) {
case 'c':
/* Get configuration from file */
if (strlen(optarg) < OPTION_LEN) {
have_config = 1; /* Configuration file name is supplied from command line */
if (get_config(optarg) != 0) return -16; /* Error in configuration file */
} else {
fprintf(stderr, "Invalid configuration file: %s\n", optarg);
return -1;
}
break;
case 'd':
/* Set directory which contains log files */
if (strlen(optarg) < OPTION_LEN) config.log_dir = optarg;
else {
fprintf(stderr, "Invalid directory: %s\n", optarg);
return -2;
}
break;
case 'o':
/* Set output file */
if (strlen(optarg) < OPTION_LEN) config.out_file = optarg;
else {
fprintf(stderr, "Invalid output file: %s\n", optarg);
return -3;
}
break;
case 'r':
/* Set directory for log analyzis reports */
if (strlen(optarg) < OPTION_LEN) strcpy(config.report_dir, optarg);
else {
fprintf(stderr, "Invalid log analyzis reports directory: %s\n", optarg);
return -4;
}
break;
case 'u':
/* Set directory for UBN files */
if (strlen(optarg) < OPTION_LEN) strcpy(config.ubn_dir, optarg);
else {
fprintf(stderr, "Invalid UBN files directory: %s\n", optarg);
return -5;
}
break;
case 'h':
default:
usage();
return 1;
}
}
/* Read configuation from file (if not already done) */
if (!have_config && !config.contest.valid && get_config(DEFAULT_CONFIG_FILE) != 0) {
fprintf(stderr, "Warning: Using defaults for contest configuration\n");
/* Set contest configuration defaults */
strcpy(config.contest.name, DEFAULT_CONTEST_NAME);
config.contest.id = CONTEST_ID_UNDEFINED;
time(&t);
gmtime_r(&t, &tm);
tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
config.contest.start = timegm(&tm);
config.contest.end = config.contest.start + DEFAULT_CONTEST_DURATION;
config.contest.rounds = 1;
config.contest.max_dt = DEFAULT_TIME_DIFFERENCE;
strcpy(config.contest.band_list, DEFAULT_BAND_LIST);
strcpy(config.contest.mode_list, DEFAULT_MODE_LIST);
config.contest.skip_rst_snt = 0;
config.contest.skip_rst_rcv = 0;
config.contest.skip_num_snt = 0;
config.contest.skip_num_rcv = 0;
config.contest.cfm_unresolved = DEFAULT_CFM_UNRESOLVED;
config.contest.valid = 1;
config.contest.uniq_mult = 0;
config.contest.strict_eval = 0;
config.contest.allow_dupes = 0;
config.contest.search_tags = 0;
config.contest.ignore_tags = 0;
}
/* Assign log file names supplied with command line (if any) */
prev = NULL;
while (optind < argc) {
DEBUG("Log file: %s\n", argv[optind]);
cur = alloc_list(argv[optind++]);
if (!prev) config.file_list = cur;
else prev->next = cur;
prev = cur;
}
/* Read log files from the supplied directory */
if (config.log_dir) {
/* Remove trailing slashes */
n = strlen(config.log_dir); /* Directory path length */
s = config.log_dir + n - 1;
while (n > 0 && (*s == '/' || *s == '\\')) { *s-- = '\0'; n--; }
/* Read directory contents */
DEBUG("Reading directory: %s\n", config.log_dir);
if (!(dir = opendir(config.log_dir))) {
perror("opendir");
return -12;
}
while ((dir_entry = readdir(dir))) {
/* Read each directory entry */
#ifndef __WIN32__
if (dir_entry->d_type == DT_REG) { /* Only read regular files */
#else
if (dir_entry->d_name && dir_entry->d_name[0] != '.') { /* Skip hidden files and current/parent directories */
#endif
/* Make full path to the log file */
if (strlen(dir_entry->d_name) + n >= MAX_STR - 1) fprintf(stderr, "WARNING: Log file path is too long: %s\\%s\n", config.log_dir, dir_entry->d_name);
else {
sprintf(str, "%s/%s", config.log_dir, dir_entry->d_name);
DEBUG("Log file: %s\n", str);
cur = alloc_list(str);
if (!prev) config.file_list = cur;
else prev->next = cur;
prev = cur;
}
}
}
closedir(dir);
}
/* Assign default reports & ubn directories if they are absent */
if (config.report_dir[0] == '\0') strcpy(config.report_dir, DEFAULT_REPORT_DIR);
if (config.ubn_dir[0] == '\0') strcpy(config.ubn_dir, DEFAULT_UBN_DIR);
/* Open output file */
if (!config.out_file || (config.out_file && strcasecmp(config.out_file, "stdout") == 0))
config.out_fd = stdout; /* Use stdout as output file */
else if (!(config.out_fd = fopen(config.out_file, "w"))) {
perror("fopen");
return -17; /* Unable to open output file */
}
/* Print header to the output file */
out(NULL, "*** Contest Arbitrator v.%s results file ***\n", VERSION);
if (time(&t) > 0) out(NULL, "Created: %s\n", ctime(&t));
/* Prepare functions according to contest id */
switch (config.contest.id) {
case CONTEST_ID_RUQRP_WAKEUP:
if (wakeup_config(config.contest.start, config.contest.end,
config.contest.mode_list, config.contest.band_list, config.contest.rounds) != 0) {
out(NULL, "ERROR: Unable to configure \"%s\" contest module. Aborting.\n", config.contest.name);
return -32;
}
break;
case CONTEST_ID_UNDEFINED:
out(NULL, "WARNING: Contest type is not defined in configuration file\n");
case CONTEST_ID_GENERAL:
default:
if (general_config(config.contest.start, config.contest.end,
config.contest.mode_list, config.contest.band_list, config.contest.rounds) != 0) {
out(NULL, "ERROR: Unable to configure \"%s\" contest module. Aborting.\n", config.contest.name);
return -33;
}
config.contest.id = CONTEST_ID_GENERAL; /* Forcing general contest evaluation */
}
out(NULL, "\nContest: %s\n\n", config.contest.name);
out(NULL, "Analyzing contest logs...\n\n", n);
if ((n = parse_logs(&config, &base)) < 0) {
out(NULL, "ERROR: There are an errors in the input logs. See analysis information above\n");
} else {
if (n == 0) out(NULL, "WARNING: There is seems to be no correct contest logs found. Aborting\n");
else {
out(NULL, "\n%u log files have been analyzed. Ready to start contest judgement\n", n);
/* General cross-check */
cross_check(&base, config.contest.id);
/* Contest specific cross-check */
switch (config.contest.id) {
case CONTEST_ID_RUQRP_WAKEUP:
wakeup_eval(&base);
break;
/* TODO: other contests */
case CONTEST_ID_GENERAL:
general_eval(&base);
break;
}
/* Show scoring */
score_table(config.contest.name, config.contest.start, config.contest.end, &base);
/* Update UBN files */
report_ubn(&base, &config);
/* Free memory */
switch (config.contest.id) {
case CONTEST_ID_RUQRP_WAKEUP:
wakeup_free();
break;
/* TODO: other contests */
case CONTEST_ID_GENERAL:
general_free();
break;
}
free_logs(&base); /* Release memory occupied by the database */
unresolved_free();
}
}
/* Close output file */
if (config.out_fd && config.out_fd != stdout) fclose(config.out_fd);
/* Release log files */
free_list(config.file_list);
DEBUG("<== main(%d, %p)\n", argc, argv);
return 0; /* Normal exit */
}
int out(FILE *report_fd, const char *fmt, ...) {
/* Write to output file */
int len;
va_list ap;
char s[MAX_STR];
va_start(ap, fmt);
vsnprintf(s, MAX_STR, fmt, ap);
va_end(ap);
len = strlen(s);
fwrite(s, 1, len, config.out_fd); /* Write to output file */
if (report_fd) fwrite(s, 1, len, report_fd); /* Write to report file */
return len;
}
static void usage(void) {
/* Print usage information */
DEBUG("==> usage()\n");
printf("Usage: contesta [options] [files]\n"
" Available options are:\n"
" -h - Show usage information\n"
" -c <file> - Specify configuration file\n"
" -d <path> - Specify directory with log files (default is current directory)\n"
" -o <file> - Specify output file with results (default is stdout)\n"
" -r <path> - Specify directory for log analyzis reports (default is ./%s)\n"
" -u <path> - Specify directory for UBN files (default is ./%s)\n"
"Please report bugs to ur4mck@gmail.com\n", DEFAULT_REPORT_DIR, DEFAULT_UBN_DIR);
DEBUG("<== usage()\n");
}
static int get_config(char *file) {
/* Read configuration from file */
FILE *fd;
char s[MAX_STR], tmp[MAX_STR], *p, *name, *value;
int name_ready;
unsigned int line_num, errors;
DEBUG("==> get_config(%s)\n", file);
/* Open file */
if (!(fd = fopen(file, "r"))) {
fprintf(stderr, "ERROR: Configuration file '%s' was not found\n", file);
return -1;
}
/* Read configuration file */
line_num = errors = 0;
while (!feof(fd) && fgets(s, MAX_STR, fd)) {
line_num++; /* Line numbers counter */
if (s[0] == '\0' || s[0] == '\n' || s[0] == '\r') continue; /* Skip blank line */
strcpy(tmp, s); /* Preserve original string for error reporting */
strip_leading_spaces(s); /* Remove leading spaces */
/* Parse configuration file line */
p = s;
name_ready = 0;
name = value = NULL;
while (*p != '\0') {
if (*p == CONFIG_FILE_COMMENT_CHAR) {
/* Skip text after the commentary char */
*p = '\0';
break;
}
if (!name_ready) {
if (!name) name = p; /* Start of the name */
else if (*p == ' ' || *p == '\t') {
*p = '\0'; /* End of the name */
name_ready = 1;
value = ++p; /* Start of the value */
}
}
p++;
}
if (name_ready) {
strip_leading_spaces(value);
strip_trailing_spaces(value);
if (parse_config(name, value, &p) != 0) {
fprintf(stderr, "Error in configuration file, line %u:\n%s - %s\n\n", line_num, tmp, p);
errors++;
}
}
}
fclose(fd);
if (errors) {
fprintf(stderr, "There was an %u error%s in configuration file %s\nAborting\n", errors, errors > 1 ? "(s)" : "", file);
config.contest.valid = 0; /* Contest configuration is not valid due to errors */
return 1;
} else config.contest.valid = 1; /* Contest configuration is valid now */
/* Check critical configuration options */
if (config.contest.name[0] == '\0') strcpy(config.contest.name, DEFAULT_CONTEST_NAME);
if (config.contest.band_list[0] == '\0') strcpy(config.contest.band_list, DEFAULT_BAND_LIST);
if (config.contest.mode_list[0] == '\0') strcpy(config.contest.mode_list, DEFAULT_MODE_LIST);
if (config.contest.cfm_unresolved < DEFAULT_CFM_UNRESOLVED) config.contest.cfm_unresolved = DEFAULT_CFM_UNRESOLVED;
DEBUG("<== get_config(%s)\n", file);
return 0; /* Success */
}
static int parse_config(char *name, char *value, char **err_msg) {
/* Parse configuration file setting name and its value */
int n;
struct tm tm;
char tmp[MAX_STR];
DEBUG("==> parse_config('%s', '%s', %p)\n", name, value, err_msg);
while (1) { /* dummy */
/* Contest name */
if (strcasecmp(name, CONFIG_PARAM_NAME) == 0) {
if ((n = strlen(value)) > 0) {
strncpy(config.contest.name, &value[1], n - 2);
config.contest.name[n - 2] = '\0';
} else {
*err_msg = "Contest name is empty";
return 1;
}
break;
}
/* Contest ID */
if (strcasecmp(name, CONFIG_PARAM_ID) == 0) {
if (strlen(value) > 0) {
n = atoi(value);
if (n > 0) config.contest.id = n;
else {
*err_msg = "Contest ID is incorrect";
return 2;
}
} else {
*err_msg = "Contest ID is not defined";
return 2;
}
break;
}
/* Contest start date */
if (strcasecmp(name, CONFIG_PARAM_START) == 0) {
if ((n = strlen(value)) > 0) {
strncpy(tmp, &value[1], n - 2);
tmp[n - 2] = '\0';
if (strptime(tmp, "%Y-%m-%d %H%M", &tm)) {
tm.tm_sec = 0;
config.contest.start = timegm(&tm);
} else {
*err_msg = "Contest starting date/time is incorrect";
return 3;
}
} else {
*err_msg = "Contest starting date/time is not defined";
return 3;
}
break;
}
/* Contest end date */
if (strcasecmp(name, CONFIG_PARAM_END) == 0) {
if ((n = strlen(value)) > 0) {
strncpy(tmp, &value[1], n - 2);
tmp[n - 2] = '\0';
if (strptime(tmp, "%Y-%m-%d %H%M", &tm)) {
tm.tm_sec = 0;
config.contest.end = timegm(&tm);
} else {
*err_msg = "Contest ending date/time is incorrect";
return 4;
}
} else {
*err_msg = "Contest ending date/time is not defined";
return 4;
}
break;
}
/* Number of rounds */
if (strcasecmp(name, CONFIG_PARAM_ROUNDS) == 0) {
if (strlen(value) > 0) {
n = atoi(value);
if (n >= 0) config.contest.rounds = n;
else {
*err_msg = "Number of rounds is wrong";
return 5;
}
} else {
*err_msg = "Number of rounds is not defined";
return 5;
}
break;
}
/* Maximum difference in time */
if (strcasecmp(name, CONFIG_PARAM_MAX_DT) == 0) {
if (strlen(value) > 0) config.contest.max_dt = atoi(value);
else {
*err_msg = "Incorrect parameter";
return 6;
}
break;
}
/* Band list */
if (strcasecmp(name, CONFIG_PARAM_BAND_LIST) == 0) {
if ((n = strlen(value)) > 0) {
strncpy(config.contest.band_list, &value[1], n - 2);
config.contest.band_list[n - 2] = '\0';
} else {
*err_msg = "Contest allowed band list is empty";
return 7;
}
break;
}
/* Mode list */
if (strcasecmp(name, CONFIG_PARAM_MODE_LIST) == 0) {
if ((n = strlen(value)) > 0) {
strncpy(config.contest.mode_list, &value[1], n - 2);
config.contest.mode_list[n - 2] = '\0';
} else {
*err_msg = "Contest allowed mode list is empty";
return 8;
}
break;
}
/* Signature */
if (strcasecmp(name, CONFIG_PARAM_SIGNATURE) == 0) {
if ((n = strlen(value)) > 0) {
strncpy(config.contest.signature, &value[1], n - 2);
config.contest.signature[n - 2] = '\0';
} else {
*err_msg = "Signature is empty";
return 9;
}
break;
}
/* Sent RST checking */
if (strcasecmp(name, CONFIG_PARAM_SKIP_RST_SNT) == 0) {
if (strlen(value) == 1 && (value[0] == '0' || value[0] == '1')) config.contest.skip_rst_snt = (int)(value[0] - '0');
else {
*err_msg = "Parameter is wrong";
return 10;
}
break;
}
/* Received RST checking */
if (strcasecmp(name, CONFIG_PARAM_SKIP_RST_RCV) == 0) {
if (strlen(value) == 1 && (value[0] == '0' || value[0] == '1')) config.contest.skip_rst_rcv = (int)(value[0] - '0');
else {
*err_msg = "Parameter is wrong";
return 11;
}
break;
}
/* Sent incremental number checking */
if (strcasecmp(name, CONFIG_PARAM_SKIP_NUM_SNT) == 0) {
if (strlen(value) == 1 && (value[0] == '0' || value[0] == '1')) config.contest.skip_num_snt = (int)(value[0] - '0');
else {
*err_msg = "Parameter is wrong";
return 12;
}
break;
}
/* Received incremental number checking */
if (strcasecmp(name, CONFIG_PARAM_SKIP_NUM_RCV) == 0) {
if (strlen(value) == 1 && (value[0] == '0' || value[0] == '1')) config.contest.skip_num_rcv = (int)(value[0] - '0');
else {
*err_msg = "Parameter is wrong";
return 13;
}
break;
}
/* Sent check number checking */
if (strcasecmp(name, CONFIG_PARAM_SKIP_CHK_SNT) == 0) {
if (strlen(value) == 1 && (value[0] == '0' || value[0] == '1')) config.contest.skip_chk_snt = (int)(value[0] - '0');
else {
*err_msg = "Parameter is wrong";
return 12;
}
break;
}
/* Received check number checking */
if (strcasecmp(name, CONFIG_PARAM_SKIP_CHK_RCV) == 0) {
if (strlen(value) == 1 && (value[0] == '0' || value[0] == '1')) config.contest.skip_chk_rcv = (int)(value[0] - '0');
else {
*err_msg = "Parameter is wrong";
return 13;
}
break;
}
/* Minimum number of unresolved QSOs to confirm them as valid */
if (strcasecmp(name, CONFIG_PARAM_CFM_UNRESOLVED) == 0) {
if (strlen(value) > 0) {
n = atoi(value);
if (n >= DEFAULT_CFM_UNRESOLVED) config.contest.cfm_unresolved = n;
else {
*err_msg = "Minimum number of unresolved QSOs is wrong";
return 14;
}
} else {
*err_msg = "Minimum number of unresolved QSOs is empty";
return 15;
}
break;
}
/* Log analyzis reports directory */
if (strcasecmp(name, CONFIG_PARAM_REPORT_DIR) == 0) {
if ((n = strlen(value)) > 0) {
strncpy(config.report_dir, &value[1], n - 2);
config.report_dir[n - 2] = '\0';
} else {
*err_msg = "Log analyzis reports directory is empty";
return 16;
}
break;
}
/* UBN files directory */
if (strcasecmp(name, CONFIG_PARAM_UBN_DIR) == 0) {
if ((n = strlen(value)) > 0) {
strncpy(config.ubn_dir, &value[1], n - 2);
config.ubn_dir[n - 2] = '\0';
} else {
*err_msg = "UBN files directory is empty";
return 17;
}
break;
}
/* Log submission e-mails list */
if (strcasecmp(name, CONFIG_PARAM_LOG_EMAILS) == 0) {
if ((n = strlen(value)) > 0) {
strncpy(config.contest.email, &value[1], n - 2);
config.contest.email[n - 2] = '\0';
} else {
*err_msg = "Log e-mails list is empty";
return 18;
}
break;
}
/* Official results URL */
if (strcasecmp(name, CONFIG_PARAM_WWW) == 0) {
if ((n = strlen(value)) > 0) {
strncpy(config.contest.www, &value[1], n - 2);
config.contest.www[n - 2] = '\0';
} else {
*err_msg = "Official results URL is empty (see 'www' param in config)";
return 18;
}
break;
}
/* Unique mult flag */
if (strcasecmp(name, CONFIG_PARAM_UNIQ_MULT) == 0) {
if (strlen(value) == 1 && (value[0] == '0' || value[0] == '1')) config.contest.uniq_mult = (int)(value[0] - '0');
else {
*err_msg = "Parameter is wrong";
return 19;
}
break;
}
/* Strict eval flag */
if (strcasecmp(name, CONFIG_PARAM_STRICT_EVAL) == 0) {
if (strlen(value) == 1 && (value[0] == '0' || value[0] == '1')) config.contest.strict_eval = (int)(value[0] - '0');
else {
*err_msg = "Parameter is wrong";
return 19;
}
break;
}
/* Allow duplicates flag */
if (strcasecmp(name, CONFIG_PARAM_ALLOW_DUPES) == 0) {
if (strlen(value) == 1 && (value[0] == '0' || value[0] == '1')) config.contest.allow_dupes = (int)(value[0] - '0');
else {
*err_msg = "Parameter is wrong";
return 20;
}
break;
}
/* Search tags flag */
if (strcasecmp(name, CONFIG_PARAM_SEARCH_TAGS) == 0) {
if (strlen(value) == 1 && (value[0] == '0' || value[0] == '1')) config.contest.search_tags = (int)(value[0] - '0');
else {
*err_msg = "Parameter is wrong";
return 21;
}
break;
}
/* Ignore tags flag */
if (strcasecmp(name, CONFIG_PARAM_IGNORE_TAGS) == 0) {
if (strlen(value) == 1 && (value[0] == '0' || value[0] == '1')) config.contest.ignore_tags = (int)(value[0] - '0');
else {
*err_msg = "Parameter is wrong";
return 22;
}
break;
}
*err_msg = ""; /* No errors */
break;
};
DEBUG("<== parse_config('%s', '%s', %p)\n", name, value, err_msg);
return 0; /* Success */
}
static LOG_FILE_S *alloc_list(char *name) {
/* Allocate next leaf for the log files list */
LOG_FILE_S *ptr;
DEBUG("==> alloc_list(%s)\n", name);
/* Allocate memory for the list leaf */
if (!(ptr = malloc(sizeof(LOG_FILE_S)))) {
perror("malloc");
exit(-33); /* Fatal error */
}
if (!(ptr->name = malloc(strlen(name) + 1))) {
perror("malloc");
free(ptr);
exit(-34);
}
strcpy(ptr->name, name);
ptr->next = NULL;
DEBUG("<== alloc_list(%s) = %p\n", name, ptr);
return ptr;
}
static void free_list(LOG_FILE_S *ptr) {
/* Free space from log files list */
LOG_FILE_S *next;
DEBUG("==> free_list(%p)\n", ptr);
while (ptr) {
next = ptr->next;
free(ptr->name);
free(ptr);
ptr = next;
}
DEBUG("<== free_list(%p)\n", ptr);
}
static int score_table(char *name, time_t start, time_t end, BASE_S *base) {
int i, idx, max, cfm, n;
SCORE_S *score_table, *st;
HAM_S *ham, *ptr;
QSO_S *qso;
char start_s[MAX_STR], end_s[MAX_STR];
DEBUG("==> score_table(%p)\n", base);
/* Allocate score table */
if (!(score_table = calloc(base->ham_num, sizeof(SCORE_S)))) {
out(NULL, "ERROR: Unable to allocate space for score table\n");
return -1;
}
/* Create score table (sort by scores) */
idx = 0;
do {
ptr = NULL;
max = 0;
/* Search a ham with maximum score */
for (i = 0; i < base->ham_num; i++) {
ham = base->ham[i];
if (!(ham->flags & HAM_FLAG_SCORE_TAB) && ham->actual_score >= max) {
max = ham->actual_score;
ptr = ham;
}
}
if (ptr) {
/* Add to score table */
score_table[idx].ham = ptr;
ptr->rating_place = idx + 1; /* Set ham's place in rating table */
/* Calculate total & confirmed QSO number */
max = 0; /* Now it is a QSO counter */
cfm = 0; /* Confirmed QSO counter */
qso = ptr->qso;
while (qso) {
if (qso->ham_id != -1 && (qso->checked == QSO_STATUS_CONTEST_XCHECK_OK || \
qso->checked == QSO_STATUS_CONTEST_XCHECK_ONE_SIDE_VALID)) cfm++;
max++;
qso = (QSO_S*)qso->next;
}
score_table[idx].qso = max;
score_table[idx++].cfm = cfm;
ptr->flags |= HAM_FLAG_SCORE_TAB; /* Mark this ham as appeared in score table */
} else break;
} while (idx < base->ham_num);
/* Convert contest start & end time to strings */
strftime(start_s, MAX_STR, "%Y-%m-%d %H%M", gmtime(&start));
strftime(end_s, MAX_STR, "%Y-%m-%d %H%M", gmtime(&end));
/* Print results */
out(NULL, "\n************************************************************************\n"
"Results \"%s\" %s - %s\n\n", name, start_s, end_s);
out(NULL, " CALL QSO/CFM Mults Points Total\n"
"------------------------------------------------------------------------\n");
if (idx > 0) {
for (i = 0; i < idx; i++) {
st = &score_table[i];
n = out(NULL, "%3u. %s", i + 1, st->ham->callsign);
while (n++ < 19) out(NULL, " ");
n = 19 + out(NULL, "%u/%u ", st->qso, st->cfm);
while (n++ < 31) out(NULL, " ");
n = 31 + out(NULL, "%4u ", st->ham->total_mult);
while (n++ < 40) out(NULL, " ");
n = 40 + out(NULL, "%7u ", st->ham->sum_score);
while (n++ < 47) out(NULL, " ");
out(NULL, "%16u\n", st->ham->actual_score);
}
} else out(NULL, "\n Contest rating is empty\n");
out(NULL, "\n************************************************************************\n");
/* Free memory from score table */
free(score_table);
DEBUG("<== score_table(%p)\n", base);
return 0;
}