/* cache.c - manage LDB caching
 *
 * Note that the LDB file should be locked while these functions are called.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "ldb.h"


char *ldb_cache = 0;
char *ldb_cached_file = 0;

char **ldb_cache_names = 0;
char **ldb_cache_data = 0;
long *ldb_cache_offset = 0;
char *ldb_cache_flags = 0;
long ldb_cached_entries = 0;

long ldb_cache_size = 0;
time_t ldb_cache_lastmtime = 0;


/* Make sure file "ldbfile" is cached, and that the copy in memory is up to
 * date.
 */
void ldb_load(char *ldbfile)
{
	struct stat sb;
	FILE *fptr;
	long i, n;
	char *a;

	if (!ldbfile)
		return;

	if ((!ldb_cached_file) || (strcmp(ldbfile, ldb_cached_file) != 0)) {
		free(ldb_cache);
		free(ldb_cached_file);
		ldb_cached_file = strdup(ldbfile);
		ldb_cache = 0;
		ldb_cache_size = 0;
		ldb_cache_lastmtime = 0;
		ldb_cached_entries = 0;
	}

	if (stat(ldbfile, &sb))
		return;
	if ((ldb_cache_lastmtime == sb.st_mtime)
	    && (sb.st_size == ldb_cache_size - 1))
		return;

	fptr = fopen(ldbfile, "r");
	if (!fptr)
		return;

	ldb_cache_size = sb.st_size + 1;

	free(ldb_cache_names);
	free(ldb_cache_data);
	free(ldb_cache_offset);
	free(ldb_cache_flags);
	ldb_cache_names = 0;
	ldb_cache_data = 0;
	ldb_cache_offset = 0;
	ldb_cache_flags = 0;
	ldb_cached_entries = 0;

	ldb_cache = calloc(ldb_cache_size, 1);
	if (!ldb_cache) {
		fclose(fptr);
		ldb_cache_size = 0;
		free(ldb_cached_file);
		ldb_cached_file = 0;
		return;
	}

	fseek(fptr, 0, SEEK_SET);
	fread(ldb_cache, 1, ldb_cache_size - 1, fptr);
	fclose(fptr);

	ldb_cache_lastmtime = sb.st_mtime;

	for (i = 0; i < ldb_cache_size; i++) {
		if (ldb_cache[i] == '\n') {
			ldb_cache[i] = 0;
			ldb_cached_entries++;
		}
	}

	if (ldb_cached_entries == 0)
		return;

	ldb_cache_names = calloc(ldb_cached_entries, sizeof(char *));
	ldb_cache_data = calloc(ldb_cached_entries, sizeof(char *));
	ldb_cache_offset = calloc(ldb_cached_entries, sizeof(long));
	ldb_cache_flags = calloc(ldb_cached_entries, sizeof(char));

	i = 0;
	for (n = 0; n < ldb_cached_entries; n++) {
		ldb_cache_offset[n] = i;
		ldb_cache_names[n] = &(ldb_cache[i]);
		a = strchr(ldb_cache_names[n], 1);
		if (!a)
			continue;
		*a = 0;
		i += strlen(ldb_cache_names[n]);
		i += 2;
		ldb_cache_data[n] = &(ldb_cache[i]);
		i += strlen(ldb_cache_data[n]);
		i++;
	}
}


/* Store any changes to "ldbfile". Assumes that the cached copy of "ldbfile"
 * is more recent than the disk copy, so the sequence of events externally
 * should be: lock file, ldb_load(), change entries, ldb_save(), unlock.
 */
void ldb_save(char *ldbfile)
{
	struct stat sb;
	FILE *fptr;
	char d = 0;
	long n;

	if (!ldbfile)
		return;

	for (n = 0; n < ldb_cached_entries; n++) {
		if (ldb_cache_flags[n] & LDB_FLAG_DELETED)
			d = 1;
	}

	if (d) {			    /* deletions, so rewrite whole file */
		fptr = fopen(ldbfile, "w");
		if (!fptr)
			return;
		for (n = 0; n < ldb_cached_entries; n++) {
			ldb_cache_flags[n] |= LDB_FLAG_ALTERED;
		}
	} else {			    /* no deletions - just modify portions */
		fptr = fopen(ldbfile, "r+");
		if (!fptr)
			return;
	}

	for (n = 0; n < ldb_cached_entries; n++) {
		if (ldb_cache_flags[n] & LDB_FLAG_DELETED)
			continue;
		if (ldb_cache_flags[n] & LDB_FLAG_ALTERED) {
			if (!d)
				fseek(fptr, ldb_cache_offset[n], SEEK_SET);
			fprintf(fptr, "%s%c %s\n", ldb_cache_names[n], 1,
				ldb_cache_data[n]);
		}
	}

	fclose(fptr);

	if (d) {			    /* reload data if deleted any entries */
		ldb_load(ldbfile);
	} else {			    /* just set last-mod-time to now otherwise */
		stat(ldbfile, &sb);
		ldb_cache_lastmtime = sb.st_mtime;
	}
}

/* EOF */
