nakedmud-mod/
nakedmud-mod/html/tutorials/
nakedmud-mod/html/tutorials/building_extras/
nakedmud-mod/html/tutorials/c/
nakedmud-mod/html/tutorials/reference/
nakedmud-mod/html/tutorials/scripting/
nakedmud-mod/html/tutorials/scripting_extras/
nakedmud-mod/lib/
nakedmud-mod/lib/help/A/
nakedmud-mod/lib/help/B/
nakedmud-mod/lib/help/C/
nakedmud-mod/lib/help/D/
nakedmud-mod/lib/help/G/
nakedmud-mod/lib/help/H/
nakedmud-mod/lib/help/J/
nakedmud-mod/lib/help/L/
nakedmud-mod/lib/help/M/
nakedmud-mod/lib/help/O/
nakedmud-mod/lib/help/P/
nakedmud-mod/lib/help/R/
nakedmud-mod/lib/help/S/
nakedmud-mod/lib/help/W/
nakedmud-mod/lib/logs/
nakedmud-mod/lib/misc/
nakedmud-mod/lib/players/
nakedmud-mod/lib/pymodules/polc/
nakedmud-mod/lib/txt/
nakedmud-mod/lib/world/
nakedmud-mod/lib/world/zones/examples/
nakedmud-mod/lib/world/zones/examples/mproto/
nakedmud-mod/lib/world/zones/examples/oproto/
nakedmud-mod/lib/world/zones/examples/reset/
nakedmud-mod/lib/world/zones/examples/rproto/
nakedmud-mod/lib/world/zones/examples/trigger/
nakedmud-mod/lib/world/zones/limbo/
nakedmud-mod/lib/world/zones/limbo/room/
nakedmud-mod/lib/world/zones/limbo/rproto/
nakedmud-mod/src/alias/
nakedmud-mod/src/dyn_vars/
nakedmud-mod/src/editor/
nakedmud-mod/src/example_module/
nakedmud-mod/src/help2/
nakedmud-mod/src/set_val/
nakedmud-mod/src/socials/
nakedmud-mod/src/time/
//******************************************************************************
//
// storage.c
//
// It would be nice to have a standards for storing and loading data from files.
// I looked at XML, but the language didn't really appeal to me. YAML
// (http://www.yaml.org) caught my eye, but I did not manage to find a set of
// YAML utilities for C, so I decided to try and design my own conventions for
// storing data. Most of them are based on what I learned from my (brief)
// exposure to YAML.
//
//******************************************************************************

#include <time.h>
#include "mud.h"
#include "utils.h"    // trim
#include "storage.h"



//*****************************************************************************
//
// local datastructures, functions, and variables
//
//*****************************************************************************
#define SET_MARKER            '-'
#define LIST_MARKER           '='
#define STRING_MARKER         '~'
#define TYPELESS_MARKER       ' '


struct storage_set {
  HASHTABLE *entries;
  int    longest_key;
  int      top_entry;
};

struct storage_set_list {
  LIST            *list;
  LIST_ITERATOR *list_i;
};

typedef struct storage_data {
  char                  *key; // what is the variable name we're known by?
  char              *str_val; // what is the data's string value?
  STORAGE_SET_LIST *list_val; // list value?
  STORAGE_SET       *set_val; // set value?
  int              entry_num; // used by storage_set to keep track of order
} STORAGE_DATA;



/* local functions */
void delete_storage_set (STORAGE_SET *set);
void delete_storage_list(STORAGE_SET_LIST *list);
void delete_storage_data(STORAGE_DATA *data);

bool list_is_empty(STORAGE_SET_LIST *list);
bool set_is_empty (STORAGE_SET *set);

void write_storage_set(STORAGE_SET *set, FILEBUF *fb, int indent);
void write_storage_list(STORAGE_SET_LIST *list, FILEBUF *fb, int indent);
void write_storage_data(STORAGE_DATA *data, FILEBUF *fb, int key_width,int indent);


void delete_storage_set(STORAGE_SET *set) {
  HASH_ITERATOR *hash_i = newHashIterator(set->entries);
  STORAGE_DATA    *data = NULL;
  const char       *key = NULL;
  
  ITERATE_HASH(key, data, hash_i)
    delete_storage_data(data);
  deleteHashIterator(hash_i);

  deleteHashtable(set->entries);
  free(set);
}

void delete_storage_list(STORAGE_SET_LIST *list) {
  if(list->list_i) deleteListIterator(list->list_i);
  deleteListWith(list->list, delete_storage_set);
  free(list);
}

void delete_storage_data(STORAGE_DATA *data) {
  if(data->key)      free(data->key);
  if(data->str_val)  free(data->str_val);
  if(data->list_val) delete_storage_list(data->list_val);
  if(data->set_val)  delete_storage_set(data->set_val);
  free(data);
}

STORAGE_DATA *new_storage_data(const char *key) {
  STORAGE_DATA *data = calloc(1, sizeof(STORAGE_DATA));
  data->key     = strdup(key);
  return data;
}

STORAGE_DATA *new_data_set(STORAGE_SET *val, const char *key) {
  STORAGE_DATA *data = new_storage_data(key);
  data->set_val  = val;
  data->list_val = new_storage_list();
  data->str_val  = strdup("");
  return data;
}

STORAGE_DATA   *new_data_list(STORAGE_SET_LIST *val, const char *key) {
  STORAGE_DATA *data = new_storage_data(key);
  data->list_val = val;
  data->set_val  = new_storage_set();
  data->str_val  = strdup("");
  return data;
}

STORAGE_DATA *new_data_string(const char *val, const char *key) {
  STORAGE_DATA *data = new_storage_data(key);
  data->str_val      = strdup(val);
  data->list_val     = new_storage_list();
  data->set_val      = new_storage_set();
  return data;
}

STORAGE_DATA    *new_data_bool(bool val, const char *key) {
  char str_val[4]; sprintf(str_val, (val ? "yes" : "no"));
  return new_data_string(str_val, key);
}

STORAGE_DATA    *new_data_int(int val, const char *key) {
  char str_val[20]; snprintf(str_val, 20, "%d", val);
  return new_data_string(str_val, key);
}

STORAGE_DATA    *new_data_long(long val, const char *key) {
  char str_val[20]; snprintf(str_val, 20, "%ld", val);
  return new_data_string(str_val, key);
}

STORAGE_DATA *new_data_double(double val, const char *key) {
  char str_val[20]; snprintf(str_val, 20, "%lf", val);
  return new_data_string(str_val, key);
}


void print_indent(FILEBUF *fb, int indent) {
  if(indent > 0) {
    char fmt[20];
    sprintf(fmt, "%%%ds", indent);
    fbprintf(fb, fmt, " ");
  }
}


//
// Print a key and the key delimeter to file
//
void print_key(FILEBUF *fb, const char *key, int key_width, int indent) {
  char fmt[30];
  sprintf(fmt, "%%-%ds:", key_width);
  print_indent(fb, indent);
  fbprintf(fb, fmt, key);
}

//
// write a string containing newlines to a file
//
void write_string_data(const char *string, FILEBUF *fb, int indent) {
  static char buf[SMALL_BUFFER];
  int i, str_i, do_indent;
  *buf = '\0';
  do_indent = TRUE;

  for(i = str_i = 0; string[str_i] != '\0'; str_i++) {
    buf[i++] = string[str_i];
    if(i == SMALL_BUFFER-1 || string[str_i] == '\n') {
      if(do_indent == TRUE) {
	print_indent(fb, indent);
	do_indent = FALSE;
      }
      buf[i] = '\0';
      fbprintf(fb, "%s", buf);
      i = 0;
      if(string[str_i] == '\n')
	do_indent = TRUE;
    }
  }
}


//
// return true if the storage set is empty, and false if it is not
//
bool set_is_empty(STORAGE_SET *set) {
  // if the hashtable has a size of 0, we know for sure it's empty
  if(hashSize(set->entries) > 0) {
    HASH_ITERATOR *hash_i = newHashIterator(set->entries);
    STORAGE_DATA    *data = NULL;
    const char       *key = NULL;

    ITERATE_HASH(key, data, hash_i) {
      if(*data->str_val || 
	 !set_is_empty(data->set_val) || !list_is_empty(data->list_val)) {
	deleteHashIterator(hash_i);
	return FALSE;
      }
    }
    deleteHashIterator(hash_i);
  }

  return TRUE;
}


//
// return true if the storage list is empty, and false if it is not
//
bool list_is_empty(STORAGE_SET_LIST *list) {
  // if the list size is 0, we know for sure it's empty
  if(listSize(list->list) > 0) {
    LIST_ITERATOR *list_i = newListIterator(list->list);
    STORAGE_SET      *set = NULL;
    ITERATE_LIST(set, list_i) {
      if(!set_is_empty(set)) {
	deleteListIterator(list_i);
	return FALSE;
      }
    }
    deleteListIterator(list_i);
  }
  return TRUE;
}


void write_storage_data(STORAGE_DATA *data, FILEBUF *fb, int key_width,int indent){
  // first, we see if we have a string value. If we do, print it
  if(*data->str_val) {
    print_key(fb, data->key, key_width, indent);
    // if we have a newline in our string, we have to write
    // it in a special way so as to preserve the lines
    if(count_letters(data->str_val, '\n', strlen(data->str_val)) > 0) {
      // first, print the string marker and skip down to a newline
      fbprintf(fb, "%c\n", STRING_MARKER);
      // now, write the string
      write_string_data(data->str_val, fb, indent+2);
    }
    else
      fbprintf(fb, "%c%s\n", TYPELESS_MARKER, data->str_val);
  }

  // If that fails, check if we have a set value. If we do, print it
  else if(!set_is_empty(data->set_val)) {
    print_key(fb, data->key, key_width, indent);
    fbprintf(fb, "%c\n", SET_MARKER);
    write_storage_set(data->set_val, fb, indent+2);
  }

  // otherwise, check if we have a list value. If we do, print it
  else if(!list_is_empty(data->list_val)) {
    print_key(fb, data->key, key_width, indent);
    fbprintf(fb, "%c\n", LIST_MARKER);
    write_storage_list(data->list_val, fb, indent+2);
  }
}


//
// Compares two variables on their entry_num. This is used by
// write_storage_set to print data out in the same order it was
// entered.
//
int cmp_storage_vars(STORAGE_DATA *data1, STORAGE_DATA *data2) {
  if(data1->entry_num == data2->entry_num)
    return 0;
  else if(data1->entry_num < data2->entry_num)
    return -1;
  return 1;
}


void write_storage_set(STORAGE_SET *set, FILEBUF *fb, int indent) {
  // collect all of the items in our hashtable
  LIST           *elems = newList();
  HASH_ITERATOR *hash_i = newHashIterator(set->entries);
  STORAGE_DATA    *data = NULL;
  const char       *key = NULL;

  ITERATE_HASH(key, data, hash_i)
    listPut(elems, data);
  deleteHashIterator(hash_i);

  // now, sort them all
  listSortWith(elems, cmp_storage_vars);

  // now, for each one, print it
  while( (data = listPop(elems)) != NULL)
    write_storage_data(data, fb, set->longest_key, indent);
  deleteList(elems);

  // print our indent and the end-of-set marker
  print_indent(fb, indent);
  fbprintf(fb, "%c\n", SET_MARKER);
}


void write_storage_list(STORAGE_SET_LIST *list, FILEBUF *fb, int indent) {
  LIST_ITERATOR *list_i = newListIterator(list->list);
  STORAGE_SET      *set = NULL;

  ITERATE_LIST(set, list_i)
    write_storage_set(set, fb, indent);
  deleteListIterator(list_i);
}


//*****************************************************************************
//
// All of the functions required for parsing data out of files can be found
// here.
//
//*****************************************************************************

/* local functions */
STORAGE_SET      *parse_storage_set(FILEBUF *fb, int indent);
STORAGE_SET_LIST *parse_storage_list(FILEBUF *fb, int indent);


//
// skip ahead in our indent. If we can skip that far ahead,
// return TRUE. otherwise, return FALSE.
//
bool skip_indent(FILEBUF *fb, int indent) {
  int i;
  char c;
  for(i = 0; i < indent; i++) {
    c = fbgetc(fb);
    if(c != ' ')
      break;
  }

  if(i != indent) {
    fbseek(fb, -(i+1), SEEK_CUR);
    return FALSE;
  }
  else
    return TRUE;
}


//
// Check to see if we're at the end of a storage entry. If we are,
// return true. otherwise, return false.
//
bool storage_end(FILEBUF *fb) {
  char c = fbgetc(fb);
  if(c == SET_MARKER || c == EOF) {
    // also skip the newline that comes after us
    fbgetc(fb);
    return TRUE;
  }
  else {
    fbseek(fb, -1, SEEK_CUR);
    return FALSE;
  }
}


//
// return the type of the data we're dealing with. It is assumed
// this will be called IMMEDIATELY after parse_key is called
//
char parse_type(FILEBUF *fb) {
  return fbgetc(fb);
}


//
// Parse the name of the key that is immediately in front of us
//
char *parse_key(FILEBUF *fb) {
  static char buf[SMALL_BUFFER];
  char c;
  int  i = 0;
  // parse up to the colon, which is the marker for the end of the key
  while((c = fbgetc(fb)) != EOF) {
    if(c == ':') {
      buf[i] = '\0';
      break;
    }
    buf[i++] = c;
  }
  // trim off all of the trailing spaces and return a copy
  trim(buf);
  return strdup(buf);
}


//
// Read until we hit a newline. Return a copy of what we find
//
char *parse_line(FILEBUF *fb) {
  static BUFFER *buf = NULL;
  static char   sbuf[SMALL_BUFFER];
  int              i = 0;
  if(buf == NULL)
    buf = newBuffer(1);
  bufferClear(buf);
  *sbuf = 0;

  // fill up our small buffer to max, then copy it over
  for(i = 0; (sbuf[i] = fbgetc(fb)) != EOF && sbuf[i] != '\n';) {
    if(i < SMALL_BUFFER-2)
      i++;
    else {
      sbuf[++i] = '\0';
      bufferCat(buf, sbuf);
      i = 0;
      sbuf[i] = '\0';
    }
  }

  // we found the newline
  if(sbuf[i] == '\n')
    sbuf[i] = '\0';
  bufferCat(buf, sbuf);
  return strdup(bufferString(buf));
}


//
// read in a string that may possibly have multiple newlines in it
//
char *parse_string(FILEBUF *fb, int indent) {
  static BUFFER *buf = NULL;
  char          *ptr = NULL;
  if(buf == NULL)
    buf = newBuffer(1);
  bufferClear(buf);

  // as long as we can skip up our indent, we can read in data and
  // all is good. Once we can no longer skip up our indent, then
  // we have come to the end of our string
  while(skip_indent(fb, indent)) {
    ptr = parse_line(fb);
    bufferCat(buf, ptr);
    bufferCat(buf, "\n");
    free(ptr);
  }

  return strdup(bufferString(buf));
}


//
// Read in a list of storage sets. Return what we find.
//
STORAGE_SET_LIST *parse_storage_list(FILEBUF *fb, int indent) {
  STORAGE_SET_LIST *list = new_storage_list();
  STORAGE_SET       *set = NULL;

  // read in each set in our list
  while( (set = parse_storage_set(fb, indent)) != NULL)
    storage_list_put(list, set);
  return list;
}


//
// Parse one storage set and return it
//
STORAGE_SET *parse_storage_set(FILEBUF *fb, int indent) {
  STORAGE_SET *set = new_storage_set();
  int        loops = 0;

  while(skip_indent(fb, indent)) {
    loops++;
    if(storage_end(fb))
      break;

    char *key = parse_key(fb);
    char type = parse_type(fb);

    switch(type) {
    case TYPELESS_MARKER: {
      char *line = parse_line(fb);
      store_string(set, key, line);
      free(line);
      break;
    }

    case STRING_MARKER: {
      fbgetc(fb); // kill the newline
      char *string = parse_string(fb, indent+2);
      store_string(set, key, string);
      free(string);
      break;
    }

    case SET_MARKER:
      fbgetc(fb); // kill the newline
      store_set(set, key, parse_storage_set(fb, indent+2));
      break;

    case LIST_MARKER:
      fbgetc(fb); // kill the newline
      store_list(set, key, parse_storage_list(fb, indent+2));
      break;
    }
    free(key);
  }

  if(loops == 0) {
    delete_storage_set(set);
    return NULL;
  }
  return set;
}




//*****************************************************************************
//
// implementation of storage.h
// documentation contained in storage.h
//
//*****************************************************************************
void storage_write(STORAGE_SET *set, const char *fname) {
  FILEBUF *fb = NULL;
  // we wanted to open a file, but we couldn't ... abort
  if((fb = fbopen(fname, "w+")) == NULL)
    return;
  write_storage_set(set, fb, 0);
  fbclose(fb);
}


STORAGE_SET *new_storage_set() {
  STORAGE_SET *set = malloc(sizeof(STORAGE_SET));
  set->entries     = newHashtableSize(20);
  set->longest_key = 0;
  set->top_entry   = 0;
  return set;
}


void storage_close(STORAGE_SET *set) {
  delete_storage_set(set);
}


STORAGE_SET *storage_read(const char *fname) {
  FILEBUF *fb = NULL;
  // we wanted to open a file, but we couldn't ... return an empty set
  if((fb = fbopen(fname, "r")) == NULL)
    return NULL;//    return new_storage_set();

  // track how long it takes us to parse a storage set
  // struct timeval start_time;
  // gettimeofday(&start_time, NULL);

  STORAGE_SET *set = parse_storage_set(fb, 0);

  // finish tracking
  // struct timeval end_time;
  // gettimeofday(&end_time, NULL);
  // int usecs = (int)(end_time.tv_usec - start_time.tv_usec);
  // int secs  = (int)(end_time.tv_sec  - start_time.tv_sec);
  // log_string("storage read time %d %s", (int)(secs*1000000 + usecs), fname);

  fbclose(fb);
  return set;
}

STORAGE_SET_LIST *new_storage_list() {
  STORAGE_SET_LIST *list = malloc(sizeof(STORAGE_SET_LIST));
  list->list = newList();
  list->list_i = NULL;
  return list;
}

STORAGE_SET *storage_list_next(STORAGE_SET_LIST *list) {
  if(list->list_i == NULL)
    list->list_i = newListIterator(list->list);
  else
    listIteratorNext(list->list_i);
  return listIteratorCurrent(list->list_i);
}

void storage_list_put(STORAGE_SET_LIST *list, STORAGE_SET *set) {
  listQueue(list->list, set);
}

void   storage_put(STORAGE_SET *set, STORAGE_DATA *data) {
  // see if it's our longest key
  int key_len      = strlen(data->key);
  set->longest_key = MAX(set->longest_key, key_len);
  // if we already have data by this name, delete it
  STORAGE_DATA *olddata = hashGet(set->entries, data->key);
  if(olddata) delete_storage_data(olddata);
  // add the new data
  hashPut(set->entries, data->key, data);
  data->entry_num = ++set->top_entry;
}


void   store_set(STORAGE_SET *set, const char *key, STORAGE_SET *val) {
  storage_put(set, new_data_set(val, key));
}

void   store_list(STORAGE_SET *set, const char *key, STORAGE_SET_LIST *val) {
  storage_put(set, new_data_list(val, key));
}

void store_string(STORAGE_SET *set, const char *key, const char *val) {
  storage_put(set, new_data_string(val, key));
}

void store_double(STORAGE_SET *set, const char *key, double val) {
  storage_put(set, new_data_double(val, key));
}

void store_bool(STORAGE_SET *set, const char *key, bool val) {
  storage_put(set, new_data_bool(val, key));
}

void store_int(STORAGE_SET *set, const char *key, int val) {
  storage_put(set, new_data_int(val, key));
}

void store_long(STORAGE_SET *set, const char *key, long val) {
  storage_put(set, new_data_long(val, key));
}

STORAGE_SET *read_set(STORAGE_SET *set, const char *key) {
  STORAGE_DATA *data = hashGet(set->entries, key);
  if(data) 
    return data->set_val;
  else {
    store_set(set, key, new_storage_set());
    return read_set(set, key);
  }
}

STORAGE_SET_LIST *read_list(STORAGE_SET *set, const char *key) {
  STORAGE_DATA *data = hashGet(set->entries, key);
  if(data) 
    return data->list_val;
  else {
    store_list(set, key, new_storage_list());
    return read_list(set, key);
  }
}

const char *read_string(STORAGE_SET *set, const char *key) {
  STORAGE_DATA *data = hashGet(set->entries, key);
  if(data) return data->str_val;
  else     return "";
}

bool read_bool(STORAGE_SET *set, const char *key) {
  STORAGE_DATA *data = hashGet(set->entries, key);
  if(data == NULL) 
    return FALSE;
  else if(!strcasecmp(data->str_val, "Yes"))
    return TRUE;
  else if(atoi(data->str_val) != 0)
    return TRUE;
  else
    return FALSE;
}

double read_double(STORAGE_SET *set, const char *key) {
  STORAGE_DATA *data = hashGet(set->entries, key);
  if(data) return atof(data->str_val);
  else     return 0;
}

int read_int(STORAGE_SET *set, const char *key) {
  STORAGE_DATA *data = hashGet(set->entries, key);
  if(data) return atoi(data->str_val);
  else     return 0;
}

long read_long(STORAGE_SET *set, const char *key) {
  STORAGE_DATA *data = hashGet(set->entries, key);
  if(data) return atol(data->str_val);
  else     return 0;
}

bool storage_contains(STORAGE_SET *set, const char *key) {
  return (hashGet(set->entries, key) != NULL);
}

//
// make a storage list out of a normal MUD list
//
STORAGE_SET_LIST *gen_store_list(LIST *list, void *storer) {
  STORAGE_SET *(* store_func)(void *) = storer;
  STORAGE_SET_LIST *set_list = new_storage_list();

  LIST_ITERATOR *list_i = newListIterator(list);
  void            *elem = NULL;

  ITERATE_LIST(elem, list_i)
    storage_list_put(set_list, store_func(elem));
  deleteListIterator(list_i);
  return set_list;
}


//
// parse a MUD list out of a storage list
//
LIST *gen_read_list(STORAGE_SET_LIST *list, void *reader) {
  void *(* read_func)(STORAGE_SET *) = reader;
  LIST *newlist = newList();

  STORAGE_SET *elem = NULL;
  while( (elem = storage_list_next(list)) != NULL)
    listQueue(newlist, read_func(elem));

  return newlist;
}