diff --git a/src/database_flatfile.c b/src/database_flatfile.c index 629d1908..ee7d76df 100644 --- a/src/database_flatfile.c +++ b/src/database_flatfile.c @@ -205,6 +205,15 @@ _ff_state_build(ff_contact_state_t* state) state->bom_len = ff_skip_bom(fp); + int file_version = ff_read_format_version(fp); + if (file_version == 0) { + log_warning("flatfile: %s has no format-version marker, expected %d", + state->filepath, FLATFILE_FORMAT_VERSION); + } else if (file_version != FLATFILE_FORMAT_VERSION) { + log_warning("flatfile: %s format-version %d does not match expected %d", + state->filepath, file_version, FLATFILE_FORMAT_VERSION); + } + state->n_entries = 0; state->total_lines = 0; diff --git a/src/database_flatfile.h b/src/database_flatfile.h index c0c978f5..86fdf619 100644 --- a/src/database_flatfile.h +++ b/src/database_flatfile.h @@ -24,8 +24,13 @@ // --- Constants --- -#define DIR_FLATLOG "flatlog" -#define FLATFILE_HEADER "# profanity chat log — UTF-8, LF line endings\n# vim: set fileencoding=utf-8 fileformat=unix :\n" +#define DIR_FLATLOG "flatlog" +#define FLATFILE_FORMAT_VERSION 1 +#define FF_VERSION_PREFIX "# format-version: " +#define FLATFILE_HEADER \ + "# profanity chat log — UTF-8, LF line endings\n" \ + "# format-version: 1\n" \ + "# vim: set fileencoding=utf-8 fileformat=unix :\n" #define FF_MAX_LINE_LEN (10 * 1024 * 1024) /* 10 MB — reject lines longer than this */ #define FF_MAX_LMC_DEPTH 100 /* max correction chain depth */ @@ -125,6 +130,11 @@ char* ff_unescape_meta_value(const char* val); // Skip UTF-8 BOM if present; returns 3 if skipped, 0 otherwise. int ff_skip_bom(FILE* fp); +// Scan leading '#' comment lines for the format-version marker. Returns +// the version found, 0 if no marker is present, or -1 on read error. +// File position is rewound to the start of the comment block on entry. +int ff_read_format_version(FILE* fp); + char* ff_readline(FILE* fp, gboolean* truncated); void ff_write_line(FILE* fp, const char* timestamp, const char* type, const char* enc, const char* stanza_id, const char* archive_id, const char* replace_id, diff --git a/src/database_flatfile_parser.c b/src/database_flatfile_parser.c index 53d2a340..650d5790 100644 --- a/src/database_flatfile_parser.c +++ b/src/database_flatfile_parser.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -338,6 +339,49 @@ ff_skip_bom(FILE* fp) return 0; } +// Format-version helper +// ========================================================================= +// +// Scan leading '#' comment lines for `# format-version: `. Stops at +// the first non-comment line or after FF_VERSION_SCAN_MAX comments. +// Restores fp to its position at entry, so callers can re-parse the +// header normally afterwards. +#define FF_VERSION_SCAN_MAX 16 + +int +ff_read_format_version(FILE* fp) +{ + long start = ftell(fp); + if (start < 0) + return -1; + + int version = 0; + size_t prefix_len = strlen(FF_VERSION_PREFIX); + + for (int i = 0; i < FF_VERSION_SCAN_MAX; i++) { + gboolean truncated = FALSE; + char* line = ff_readline(fp, &truncated); + if (!line) + break; + if (line[0] != '#') { + free(line); + break; + } + if (strncmp(line, FF_VERSION_PREFIX, prefix_len) == 0) { + char* endptr = NULL; + long v = strtol(line + prefix_len, &endptr, 10); + if (endptr != line + prefix_len && v > 0 && v <= INT_MAX) + version = (int)v; + free(line); + break; + } + free(line); + } + + fseek(fp, start, SEEK_SET); + return version; +} + // Readline helper // ========================================================================= //