/* retoor <retoor@molodetz.nl> */
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "cache.h"
static int64_t current_time_ms(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
static int entry_is_valid(cache_entry_t *entry) {
if (!entry) return 0;
int64_t now = current_time_ms();
int64_t age = now - (int64_t)entry->created_at;
return age < entry->ttl_ms;
}
static void entry_free(cache_entry_t *entry) {
if (!entry) return;
free(entry->key);
free(entry->value);
free(entry);
}
cache_t *cache_init(int max_entries, int default_ttl_ms) {
cache_t *cache = calloc(1, sizeof(cache_t));
if (!cache) return NULL;
cache->max_entries = max_entries;
cache->default_ttl_ms = default_ttl_ms;
cache->current_entries = 0;
cache->head = NULL;
if (pthread_mutex_init(&cache->lock, NULL) != 0) {
free(cache);
return NULL;
}
return cache;
}
void cache_destroy(cache_t *cache) {
if (!cache) return;
pthread_mutex_lock(&cache->lock);
cache_entry_t *entry = cache->head;
while (entry) {
cache_entry_t *next = entry->next;
entry_free(entry);
entry = next;
}
pthread_mutex_unlock(&cache->lock);
pthread_mutex_destroy(&cache->lock);
free(cache);
}
static cache_entry_t *find_entry(cache_t *cache, const char *key,
cache_entry_t **prev) {
if (prev) *prev = NULL;
cache_entry_t *entry = cache->head;
cache_entry_t *previous = NULL;
while (entry) {
if (strcmp(entry->key, key) == 0) {
if (prev) *prev = previous;
return entry;
}
previous = entry;
entry = entry->next;
}
return NULL;
}
static void remove_entry(cache_t *cache, cache_entry_t *entry,
cache_entry_t *prev) {
if (prev) {
prev->next = entry->next;
} else {
cache->head = entry->next;
}
cache->current_entries--;
entry_free(entry);
}
static void evict_oldest(cache_t *cache) {
if (!cache->head) return;
cache_entry_t *oldest = cache->head;
cache_entry_t *oldest_prev = NULL;
cache_entry_t *entry = cache->head->next;
cache_entry_t *prev = cache->head;
while (entry) {
if (entry->created_at < oldest->created_at) {
oldest = entry;
oldest_prev = prev;
}
prev = entry;
entry = entry->next;
}
remove_entry(cache, oldest, oldest_prev);
}
int cache_get_stat(cache_t *cache, const char *key, struct stat *st) {
if (!cache || !key || !st) return -1;
pthread_mutex_lock(&cache->lock);
cache_entry_t *prev;
cache_entry_t *entry = find_entry(cache, key, &prev);
if (!entry) {
pthread_mutex_unlock(&cache->lock);
return -1;
}
if (!entry_is_valid(entry)) {
remove_entry(cache, entry, prev);
pthread_mutex_unlock(&cache->lock);
return -1;
}
memcpy(st, entry->value, sizeof(struct stat));
pthread_mutex_unlock(&cache->lock);
return 0;
}
int cache_put_stat(cache_t *cache, const char *key, const struct stat *st) {
if (!cache || !key || !st) return -1;
pthread_mutex_lock(&cache->lock);
cache_entry_t *prev;
cache_entry_t *existing = find_entry(cache, key, &prev);
if (existing) {
memcpy(existing->value, st, sizeof(struct stat));
existing->created_at = (time_t)current_time_ms();
pthread_mutex_unlock(&cache->lock);
return 0;
}
if (cache->current_entries >= cache->max_entries) {
evict_oldest(cache);
}
cache_entry_t *entry = calloc(1, sizeof(cache_entry_t));
if (!entry) {
pthread_mutex_unlock(&cache->lock);
return -1;
}
entry->key = strdup(key);
entry->value = malloc(sizeof(struct stat));
if (!entry->key || !entry->value) {
free(entry->key);
free(entry->value);
free(entry);
pthread_mutex_unlock(&cache->lock);
return -1;
}
memcpy(entry->value, st, sizeof(struct stat));
entry->value_size = sizeof(struct stat);
entry->created_at = (time_t)current_time_ms();
entry->ttl_ms = cache->default_ttl_ms;
entry->next = cache->head;
cache->head = entry;
cache->current_entries++;
pthread_mutex_unlock(&cache->lock);
return 0;
}
static dir_listing_t *copy_dir_listing(const dir_listing_t *src) {
if (!src) return NULL;
dir_listing_t *dst = calloc(1, sizeof(dir_listing_t));
if (!dst) return NULL;
if (src->count == 0) {
dst->entries = NULL;
dst->count = 0;
return dst;
}
dst->entries = calloc((size_t)src->count, sizeof(dir_entry_t));
if (!dst->entries) {
free(dst);
return NULL;
}
dst->count = src->count;
for (int i = 0; i < src->count; i++) {
dst->entries[i].name = strdup(src->entries[i].name);
dst->entries[i].is_directory = src->entries[i].is_directory;
dst->entries[i].size = src->entries[i].size;
dst->entries[i].mtime = src->entries[i].mtime;
if (!dst->entries[i].name) {
for (int j = 0; j < i; j++) {
free(dst->entries[j].name);
}
free(dst->entries);
free(dst);
return NULL;
}
}
return dst;
}
void dir_listing_free(dir_listing_t *listing) {
if (!listing) return;
for (int i = 0; i < listing->count; i++) {
free(listing->entries[i].name);
}
free(listing->entries);
free(listing);
}
int cache_get_dir(cache_t *cache, const char *key, dir_listing_t **listing) {
if (!cache || !key || !listing) return -1;
pthread_mutex_lock(&cache->lock);
cache_entry_t *prev;
cache_entry_t *entry = find_entry(cache, key, &prev);
if (!entry) {
pthread_mutex_unlock(&cache->lock);
return -1;
}
if (!entry_is_valid(entry)) {
remove_entry(cache, entry, prev);
pthread_mutex_unlock(&cache->lock);
return -1;
}
*listing = copy_dir_listing((dir_listing_t *)entry->value);
pthread_mutex_unlock(&cache->lock);
return *listing ? 0 : -1;
}
int cache_put_dir(cache_t *cache, const char *key, const dir_listing_t *listing) {
if (!cache || !key || !listing) return -1;
pthread_mutex_lock(&cache->lock);
cache_entry_t *prev;
cache_entry_t *existing = find_entry(cache, key, &prev);
if (existing) {
dir_listing_free((dir_listing_t *)existing->value);
existing->value = copy_dir_listing(listing);
existing->created_at = (time_t)current_time_ms();
pthread_mutex_unlock(&cache->lock);
return existing->value ? 0 : -1;
}
if (cache->current_entries >= cache->max_entries) {
evict_oldest(cache);
}
cache_entry_t *entry = calloc(1, sizeof(cache_entry_t));
if (!entry) {
pthread_mutex_unlock(&cache->lock);
return -1;
}
entry->key = strdup(key);
entry->value = copy_dir_listing(listing);
if (!entry->key || !entry->value) {
free(entry->key);
dir_listing_free((dir_listing_t *)entry->value);
free(entry);
pthread_mutex_unlock(&cache->lock);
return -1;
}
entry->created_at = (time_t)current_time_ms();
entry->ttl_ms = cache->default_ttl_ms;
entry->next = cache->head;
cache->head = entry;
cache->current_entries++;
pthread_mutex_unlock(&cache->lock);
return 0;
}
void cache_invalidate(cache_t *cache, const char *key) {
if (!cache || !key) return;
pthread_mutex_lock(&cache->lock);
cache_entry_t *prev;
cache_entry_t *entry = find_entry(cache, key, &prev);
if (entry) {
remove_entry(cache, entry, prev);
}
pthread_mutex_unlock(&cache->lock);
}
void cache_invalidate_prefix(cache_t *cache, const char *prefix) {
if (!cache || !prefix) return;
size_t prefix_len = strlen(prefix);
pthread_mutex_lock(&cache->lock);
cache_entry_t *entry = cache->head;
cache_entry_t *prev = NULL;
while (entry) {
cache_entry_t *next = entry->next;
if (strncmp(entry->key, prefix, prefix_len) == 0) {
if (prev) {
prev->next = next;
} else {
cache->head = next;
}
cache->current_entries--;
entry_free(entry);
} else {
prev = entry;
}
entry = next;
}
pthread_mutex_unlock(&cache->lock);
}
void cache_clear(cache_t *cache) {
if (!cache) return;
pthread_mutex_lock(&cache->lock);
cache_entry_t *entry = cache->head;
while (entry) {
cache_entry_t *next = entry->next;
entry_free(entry);
entry = next;
}
cache->head = NULL;
cache->current_entries = 0;
pthread_mutex_unlock(&cache->lock);
}