subrelay/src/subscribers.c

175 lines
4.2 KiB
C

/* subscribers.c - Subscriber pool implementation */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#include <ilib/subscribers.h>
/* Case-insensitive string comparison for email addresses */
static long subscriber_cmp(const void *a, const void *b)
{
const char *s1 = (const char *)a;
const char *s2 = (const char *)b;
return strcasecmp(s1, s2);
}
/* Helper to trim leading/trailing whitespace and newlines */
static char *trim_whitespace(char *str)
{
char *end;
while (isspace((unsigned char)*str)) str++;
if (*str == 0) return str;
end = str + strlen(str) - 1;
while (end > str && isspace((unsigned char)*end)) end--;
end[1] = '\0';
return str;
}
int subscriber_pool_init(subscriber_pool_t *pool, const char *filepath)
{
if (!pool || !filepath) return -EINVAL;
memset(pool, 0, sizeof(subscriber_pool_t));
pool->filepath = strdup(filepath);
if (!pool->filepath) return -ENOMEM;
return splay_init(&pool->tree, subscriber_cmp);
}
int subscriber_add(subscriber_pool_t *pool, const char *email)
{
if (!pool || !email || strlen(email) == 0) return -EINVAL;
/* Allocate container entry */
subscriber_entry_t *entry = calloc(1, sizeof(subscriber_entry_t));
if (!entry) return -ENOMEM;
entry->email = strdup(email);
if (!entry->email) {
free(entry);
return -ENOMEM;
}
/* Insert into splay tree using normalized email as key */
int rc = splay_insert(&pool->tree, &entry->node, entry->email);
if (rc != 0) {
/* Entry already exists or insertion error */
free(entry->email);
free(entry);
return rc;
}
return 0;
}
int subscriber_remove(subscriber_pool_t *pool, const char *email)
{
if (!pool || !email) return -EINVAL;
splay_node_t *node = splay_lookup(&pool->tree, email);
if (!node) return -ENOENT;
splay_delete(&pool->tree, node);
/* Retrieve wrapping entry struct and release memory */
subscriber_entry_t *entry = (subscriber_entry_t *)node;
free(entry->email);
free(entry);
return 0;
}
int subscriber_exists(subscriber_pool_t *pool, const char *email)
{
if (!pool || !email) return 0;
return (splay_lookup(&pool->tree, email) != NULL);
}
int subscriber_load_file(subscriber_pool_t *pool)
{
if (!pool || !pool->filepath) return -EINVAL;
FILE *fp = fopen(pool->filepath, "r");
if (!fp) {
/* If file doesn't exist yet, it will be created on first save */
if (errno == ENOENT) return 0;
return -errno;
}
char line[512];
while (fgets(line, sizeof(line), fp)) {
char *email = trim_whitespace(line);
/* Ignore empty lines and comment lines starting with '#' */
if (*email == '\0' || *email == '#') continue;
subscriber_add(pool, email);
}
fclose(fp);
return 0;
}
/* Atomic save using temporary file + rename to prevent corruption */
int subscriber_save_file(subscriber_pool_t *pool)
{
if (!pool || !pool->filepath) return -EINVAL;
char tmp_path[1024];
snprintf(tmp_path, sizeof(tmp_path), "%s.tmp.%d", pool->filepath, getpid());
FILE *fp = fopen(tmp_path, "w");
if (!fp) return -errno;
/* Write plain-text header */
fprintf(fp, "# Subrelay Subscribers List\n");
fprintf(fp, "# Do not edit manually while subrelayd is running\n\n");
/* In-order traversal across splay tree nodes */
splay_node_t *curr = splay_first(&pool->tree);
while (curr != NULL) {
subscriber_entry_t *entry = (subscriber_entry_t *)curr;
fprintf(fp, "%s\n", entry->email);
curr = splay_next(&pool->tree, curr);
}
fflush(fp);
fclose(fp);
/* Atomic swap */
if (rename(tmp_path, pool->filepath) != 0) {
unlink(tmp_path);
return -errno;
}
return 0;
}
void subscriber_pool_destroy(subscriber_pool_t *pool)
{
if (!pool) return;
/* Traverse tree and free all allocated nodes */
splay_node_t *curr = splay_first(&pool->tree);
while (curr != NULL) {
splay_node_t *next = splay_next(&pool->tree, curr);
splay_delete(&pool->tree, curr);
subscriber_entry_t *entry = (subscriber_entry_t *)curr;
free(entry->email);
free(entry);
curr = next;
}
if (pool->filepath) free(pool->filepath);
memset(pool, 0, sizeof(subscriber_pool_t));
return;
}