175 lines
4.2 KiB
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;
|
|
}
|