trainlog/tui/src/sync_history.c

181 lines
4.6 KiB
C

/**
* @file sync_history.c
* @brief Strict codec for local synchronization history lines.
*/
#include "trainlog/sync_history.h"
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#define SYNC_HISTORY_LINE_MAX 767U
static bool sync_history_timestamp_valid(const char *timestamp)
{
size_t index;
if (timestamp == NULL || strlen(timestamp) != 16U) {
return false;
}
for (index = 0U; index < 16U; ++index) {
char expected = '\0';
if (index == 2U || index == 5U) {
expected = '/';
} else if (index == 10U) {
expected = ' ';
} else if (index == 13U) {
expected = ':';
}
if (
(expected != '\0' && timestamp[index] != expected) ||
(expected == '\0' && !isdigit((unsigned char)timestamp[index]))
) {
return false;
}
}
return true;
}
const char *trainlog_sync_history_direction_code(
TrainlogSyncDirection direction
)
{
switch (direction) {
case TRAINLOG_SYNC_ANDROID_TO_PC:
return "a";
case TRAINLOG_SYNC_PC_TO_ANDROID:
return "p";
case TRAINLOG_SYNC_BIDIRECTIONAL:
return "b";
default:
return NULL;
}
}
static bool sync_history_direction_parse(
const char *text,
TrainlogSyncDirection *output
)
{
if (text == NULL || output == NULL || text[1] != '\0') {
return false;
}
if (text[0] == 'a') {
*output = TRAINLOG_SYNC_ANDROID_TO_PC;
} else if (text[0] == 'p') {
*output = TRAINLOG_SYNC_PC_TO_ANDROID;
} else if (text[0] == 'b') {
*output = TRAINLOG_SYNC_BIDIRECTIONAL;
} else {
return false;
}
return true;
}
const char *trainlog_sync_history_direction_label(
const TrainlogSyncHistoryEntry *entry
)
{
if (entry == NULL || !entry->direction_known) {
/* Legacy records predate the direction field; summary text is not a
* trustworthy substitute, especially for failure diagnostics. */
return "direction inconnue";
}
switch (entry->direction) {
case TRAINLOG_SYNC_ANDROID_TO_PC:
return "Android→PC";
case TRAINLOG_SYNC_PC_TO_ANDROID:
return "PC→Android";
case TRAINLOG_SYNC_BIDIRECTIONAL:
return "PC↔Android";
default:
return "direction inconnue";
}
}
bool trainlog_sync_history_parse_line(
const char *line,
TrainlogSyncHistoryEntry *output
)
{
char copy[SYNC_HISTORY_LINE_MAX + 1U];
char *fields[5];
char *cursor;
size_t field_count = 1U;
size_t length;
bool has_sync_id;
bool has_direction;
const char *sync_id;
const char *timestamp;
const char *status;
const char *summary;
if (line == NULL || output == NULL) {
return false;
}
length = strlen(line);
while (length > 0U && (line[length - 1U] == '\n' || line[length - 1U] == '\r')) {
--length;
}
if (length == 0U || length > SYNC_HISTORY_LINE_MAX) {
return false;
}
(void)memcpy(copy, line, length);
copy[length] = '\0';
fields[0] = copy;
for (cursor = copy; *cursor != '\0'; ++cursor) {
if (*cursor == '\t') {
if (field_count == sizeof(fields) / sizeof(fields[0])) {
return false;
}
*cursor = '\0';
fields[field_count] = cursor + 1;
++field_count;
}
}
has_sync_id = field_count >= 4U;
has_direction = field_count == 5U;
if (field_count != 3U && field_count != 4U && field_count != 5U) {
return false;
}
sync_id = has_sync_id ? fields[0] : "";
timestamp = has_sync_id ? fields[1] : fields[0];
status = has_sync_id ? fields[2] : fields[1];
summary = fields[field_count - 1U];
if (
(has_sync_id && (sync_id[0] == '\0' || strlen(sync_id) > TRAINLOG_ID_MAX)) ||
!sync_history_timestamp_valid(timestamp) ||
(strcmp(status, "0") != 0 && strcmp(status, "1") != 0) ||
summary[0] == '\0' || strlen(summary) > TRAINLOG_SYNC_SUMMARY_MAX
) {
return false;
}
(void)memset(output, 0, sizeof(*output));
(void)snprintf(output->sync_id, sizeof(output->sync_id), "%s", sync_id);
(void)snprintf(output->timestamp, sizeof(output->timestamp), "%s", timestamp);
output->success = strcmp(status, "1") == 0;
(void)snprintf(output->summary, sizeof(output->summary), "%s", summary);
if (has_direction) {
if (!sync_history_direction_parse(fields[3], &output->direction)) {
return false;
}
output->direction_known = true;
}
return true;
}