//****************************************************************************** // // 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; }