changeset 133:ffe8bbd429fa

Config file.
author Matti Hamalainen <ccr@tnsp.org>
date Sat, 30 Oct 2010 20:37:09 +0300
parents 10daf4660cae
children f367fc14021a
files nnchat.c th_config.c th_config.h
diffstat 3 files changed, 773 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/nnchat.c	Sat Oct 30 17:03:29 2010 +0300
+++ b/nnchat.c	Sat Oct 30 20:37:09 2010 +0300
@@ -6,6 +6,7 @@
 #include "libnnchat.h"
 #include <stdlib.h>
 #include "th_args.h"
+#include "th_config.h"
 #include <string.h>
 #include <errno.h>
 #ifdef __WIN32
@@ -55,6 +56,9 @@
 BOOL    setPrvMode = FALSE;
 BOOL	ignoreMode = FALSE;
 
+char    *setConfigFile = "config";
+cfgitem_t *cfg = NULL;
+
 
 /* Arguments
  */
@@ -563,6 +567,19 @@
         printMsg("Setting color to #%06x\n", optUserColor);
         nn_send_msg(sock, optUserName2, "%%2FSetFontColor%%20%%2Dcolor%%20%06X", optUserColor);
         return 0;
+    } else if (!strncasecmp(buf, "/save", 5)) {
+        /* Save configuration */
+        FILE *f = fopen(setConfigFile, "w");
+        if (f == NULL) {
+            printMsg("Could not save configuration to file '%s'!\n", setConfigFile);
+            return 0;
+        }
+        printMsg("Configuration saved in file '%s', res=%d\n",
+            setConfigFile,
+            th_cfg_write(f, setConfigFile, cfg));
+
+        fclose(f);
+        return 0;
     } else if (!strncasecmp(buf, "/w ", 3)) {
         /* Open given username's profile via firefox in a new tab */
         char *name = trimLeft(buf + 3),
@@ -766,6 +783,31 @@
         "Written and designed by Anonymous Finnish Guy (C) 2008-2010",
         "This software is freeware, use and distribute as you wish.");
     th_verbosityLevel = 0;
+
+    /* Read config */
+    {
+    cfgitem_t *tmpcfg = NULL;
+    FILE *cfgfile;
+    
+    th_cfg_add_comment(&tmpcfg, "General settings");
+    th_cfg_add_string(&tmpcfg, "username", &optUserName, NULL);
+    th_cfg_add_string(&tmpcfg, "password", &optPassword, NULL);
+    th_cfg_add_hexvalue(&tmpcfg, "color", &optUserColor, optUserColor);
+    th_cfg_add_section(&cfg, "general", tmpcfg);
+
+    tmpcfg = NULL;
+    th_cfg_add_comment(&tmpcfg, "Chat server hostname or IP address");
+    th_cfg_add_string(&tmpcfg, "host", &optServer, optServer);
+    th_cfg_add_comment(&tmpcfg, "Default port to connect to (8002 = public room, 8003 = passion pit, 8005 = members only)");
+    th_cfg_add_int(&tmpcfg, "port", &optPort, optPort);
+    th_cfg_add_section(&cfg, "server", tmpcfg);
+
+    th_cfg_add_comment(&cfg, "People to be ignored in ignore mode");
+    th_cfg_add_string(&cfg, "ignores", ignoreList, NULL);
+    
+    if ((cfgfile = fopen(setConfigFile, "r")) != NULL)
+        th_cfg_read(cfgfile, setConfigFile, cfg);
+    }
     
     /* Parse arguments */
     argsOK = th_args_process(argc, argv, optList, optListN,
@@ -1202,7 +1244,6 @@
         THMSG(1, "Closing logfile.\n");
         fclose(optLogFile);
     }
-    
-    
+
     return 0;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/th_config.c	Sat Oct 30 20:37:09 2010 +0300
@@ -0,0 +1,662 @@
+/*
+ * Very simple configuration handling functions
+ * Programmed and designed by Matti 'ccr' Hamalainen
+ * (C) Copyright 2004-2008 Tecnic Software productions (TNSP)
+ *
+ * Please read file 'COPYING' for information on license and distribution.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <stdio.h>
+#include <stdarg.h>
+#include "th_config.h"
+#include "th_util.h"
+#include "th_string.h"
+
+#define SET_MAX_BUF     (8192)
+
+
+/* Free a given configuration (the values are not free'd)
+ */
+void th_cfg_free(cfgitem_t *cfg)
+{
+    cfgitem_t *curr = cfg;
+
+    while (curr != NULL) {
+        cfgitem_t *next = curr->next;
+        
+        if (curr->type == ITEM_SECTION)
+            th_cfg_free((cfgitem_t *) curr->data);
+        
+        th_free(curr->name);
+        th_free(curr);
+        curr = next;
+    }
+}
+
+
+/* Allocate and add new item to configuration
+ */
+static cfgitem_t *th_cfg_add(cfgitem_t **cfg, char *name, int type, void *data)
+{
+    cfgitem_t *node;
+
+    if (cfg == NULL)
+        return NULL;
+
+    /* Allocate new item */
+    node = (cfgitem_t *) th_calloc(1, sizeof(cfgitem_t));
+    if (node == NULL)
+        return NULL;
+
+    /* Set values */
+    node->type = type;
+    node->data = data;
+    node->name = th_strdup(name);
+    
+    /* Insert into linked list */
+    if (*cfg != NULL) {
+        node->prev = (*cfg)->prev;
+        (*cfg)->prev->next = node;
+        (*cfg)->prev = node;
+    } else {
+        *cfg = node;
+        node->prev = node;
+    }
+    node->next = NULL;
+
+    return node;
+}
+
+
+/* Add integer type setting into give configuration
+ */
+int th_cfg_add_int(cfgitem_t ** cfg, char * name,
+              int *itemData, int itemDef)
+{
+    cfgitem_t *node;
+
+    node = th_cfg_add(cfg, name, ITEM_INT, (void *) itemData);
+    if (node == NULL)
+        return -1;
+
+    *itemData = itemDef;
+
+    return 0;
+}
+
+
+int th_cfg_add_hexvalue(cfgitem_t ** cfg, char * name,
+              int *itemData, int itemDef)
+{
+    cfgitem_t *node;
+
+    node = th_cfg_add(cfg, name, ITEM_HEX_TRIPLET, (void *) itemData);
+    if (node == NULL)
+        return -1;
+
+    *itemData = itemDef;
+
+    return 0;
+}
+
+
+/* Add unsigned integer type setting into give configuration
+ */
+int th_cfg_add_uint(cfgitem_t ** cfg, char * name,
+               unsigned int * itemData, unsigned int itemDef)
+{
+    cfgitem_t *node;
+
+    node = th_cfg_add(cfg, name, ITEM_UINT, (void *) itemData);
+    if (node == NULL)
+        return -1;
+
+    *itemData = itemDef;
+
+    return 0;
+}
+
+
+/* Add strint type setting into given configuration
+ */
+int th_cfg_add_string(cfgitem_t ** cfg, char * name,
+              char ** itemData, char * itemDef)
+{
+    cfgitem_t *node;
+
+    node = th_cfg_add(cfg, name, ITEM_STRING, (void *) itemData);
+    if (node == NULL)
+        return -1;
+
+    *itemData = th_strdup(itemDef);
+
+    return 0;
+}
+
+
+/* Add boolean type setting into given configuration
+ */
+int th_cfg_add_bool(cfgitem_t ** cfg, char * name,
+               BOOL * itemData, BOOL itemDef)
+{
+    cfgitem_t *node;
+
+    node = th_cfg_add(cfg, name, ITEM_BOOL, (void *) itemData);
+    if (node == NULL)
+        return -1;
+
+    *itemData = itemDef;
+
+    return 0;
+}
+
+
+/* Add implicit comment
+ */
+int th_cfg_add_comment(cfgitem_t ** cfg, char * comment)
+{
+    cfgitem_t *node;
+
+    node = th_cfg_add(cfg, comment, ITEM_COMMENT, NULL);
+    if (node == NULL)
+        return -1;
+
+    return 0;
+}
+
+
+/* Add new section
+ */
+int th_cfg_add_section(cfgitem_t ** cfg, char * name, cfgitem_t *data)
+{
+    cfgitem_t *node;
+    
+    node = th_cfg_add(cfg, name, ITEM_SECTION, (void *) data);
+    if (node == NULL)
+        return -1;
+
+    return 0;
+}
+
+
+/* Read a given file into configuration structure and variables
+ */
+enum {
+    PM_EOF,
+    PM_ERROR,
+    PM_NORMAL,
+    PM_COMMENT,
+    PM_NEXT,
+    PM_KEYNAME,
+    PM_KEYSET,
+    PM_STRING,
+    PM_INT,
+    PM_BOOL,
+    PM_SECTION
+};
+
+#define VADDCH(ch) if (strPos < SET_MAX_BUF) { tmpStr[strPos++] = ch; }
+#define VISEND(ch) (ch == '\r' || ch == '\n' || ch == ';' || th_isspace(c) || ch == '#')
+
+typedef struct {
+    FILE *file;
+    char *filename;
+    size_t line;
+} conffile_t;
+
+
+static void th_cfg_error(conffile_t *f, const char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    fprintf(stderr, "%s: '%s', line #%d: ", th_prog_name, f->filename, f->line);
+    vfprintf(stderr, fmt, ap);
+    va_end(ap);
+}
+
+
+static int th_cfg_read_sect(conffile_t *f, cfgitem_t * cfg, int nesting)
+{
+    cfgitem_t *item = NULL;
+    char tmpStr[SET_MAX_BUF + 1];
+    size_t strPos;
+    int c, parseMode, prevMode, nextMode, tmpCh;
+    BOOL isFound, isStart, isError, validError;
+
+    /* Initialize values */
+    tmpCh = 0;
+    strPos = 0;
+    c = -1;
+    isFound = isStart = isError = validError = FALSE;
+    nextMode = prevMode = parseMode = PM_NORMAL;
+
+    /* Parse the configuration */
+    while (parseMode != PM_EOF && parseMode != PM_ERROR) {
+        if (c == -1) {
+            /* Get next character */
+            switch (c = fgetc(f->file)) {
+            case EOF:
+                if (parseMode != PM_NORMAL) {
+                    th_cfg_error(f,
+                    "Unexpected end of file.\n");
+                    parseMode = PM_ERROR;
+                } else
+                    parseMode = PM_EOF;
+                break;
+
+            case '\n':
+                f->line++;
+            }
+        }
+
+        switch (parseMode) {
+        case PM_COMMENT:
+            /* Comment parsing mode */
+            if (c == '\n') {
+                /* End of line, end of comment */
+                parseMode = prevMode;
+                prevMode = PM_COMMENT;
+            }
+            c = -1;
+            break;
+
+        case PM_NORMAL:
+            /* Normal parsing mode */
+            if (c == '#') {
+                prevMode = parseMode;
+                parseMode = PM_COMMENT;
+                c = -1;
+            } else if (VISEND(c)) {
+                c = -1;
+            } else if (c == '}') {
+                if (nesting > 0) {
+                    /* Check for validation errors */
+                    return (validError) ? 1 : 0;
+                } else {
+                    th_cfg_error(f, "Invalid nesting sequence encountered.\n");
+                    parseMode = PM_ERROR;
+                }
+            } else if (th_isalpha(c)) {
+                /* Start of key name found */
+                prevMode = parseMode;
+                parseMode = PM_KEYNAME;
+                strPos = 0;
+            } else {
+                /* Error! Invalid character found */
+                th_cfg_error(f,
+                    "Unexpected character '%c'.\n", c);
+                parseMode = PM_ERROR;
+            }
+            break;
+
+        case PM_KEYNAME:
+            /* Configuration KEY name parsing mode */
+            if (c == '#') {
+                /* Start of comment */
+                prevMode = parseMode;
+                parseMode = PM_COMMENT;
+                c = -1;
+            } else if (th_iscrlf(c) || th_isspace(c) || c == '=') {
+                /* End of key name */
+                prevMode = parseMode;
+                parseMode = PM_NEXT;
+                nextMode = PM_KEYSET;
+            } else if (th_isalnum(c) || c == '_') {
+                /* Add to key name string */
+                VADDCH(c)
+                else
+                {
+                    /* Error! Key name string too long! */
+                    th_cfg_error(f,
+                        "Config key name too long!");
+                    parseMode = PM_ERROR;
+                }
+                c = -1;
+            } else {
+                /* Error! Invalid character found */
+                tmpStr[strPos] = 0;
+                th_cfg_error(f,
+                    "Unexpected character '%c' in key name '%s'.\n", c, tmpStr);
+                parseMode = PM_ERROR;
+            }
+            break;
+
+        case PM_KEYSET:
+            if (c == '=') {
+                /* Find key from configuration */
+                tmpStr[strPos] = 0;
+                isFound = FALSE;
+                item = cfg;
+                while (item != NULL && !isFound) {
+                    if (item->name != NULL && strcmp(item->name, tmpStr) == 0)
+                        isFound = TRUE;
+                    else
+                        item = item->next;
+                }
+
+                /* Check if key was found */
+                if (isFound) {
+                    /* Okay, set next mode */
+                    switch (item->type) {
+                    case ITEM_HEX_TRIPLET:
+                    case ITEM_STRING:
+                        nextMode = PM_STRING;
+                        break;
+
+                    case ITEM_INT:
+                    case ITEM_UINT:
+                        nextMode = PM_INT;
+                        break;
+
+                    case ITEM_BOOL:
+                        nextMode = PM_BOOL;
+                        break;
+                    
+                    case ITEM_SECTION:
+                        nextMode = PM_SECTION;
+                        break;
+                    }
+
+                    prevMode = parseMode;
+                    parseMode = PM_NEXT;
+                    isStart = TRUE;
+                    strPos = 0;
+                } else {
+                    /* Error! No configuration key by this name found */
+                    th_cfg_error(f,
+                        "No such configuration setting ('%s')\n", tmpStr);
+                    parseMode = PM_ERROR;
+                }
+
+                c = -1;
+            } else {
+                /* Error! '=' expected! */
+                th_cfg_error(f,
+                    "Unexpected character '%c', assignation '=' was expected.\n", c);
+                parseMode = PM_ERROR;
+            }
+            break;
+
+        case PM_NEXT:
+            /* Search next item parsing mode */
+            if (c == '#') {
+                /* Start of comment */
+                prevMode = parseMode;
+                parseMode = PM_COMMENT;
+            } else if (th_isspace(c) || th_iscrlf(c)) {
+                /* Ignore whitespaces and linechanges */
+                c = -1;
+            } else {
+                /* Next item found */
+                prevMode = parseMode;
+                parseMode = nextMode;
+            }
+            break;
+
+        case PM_SECTION:
+            /* Section parsing mode */
+            if (c != '{') {
+                /* Error! Section start '{' expected! */
+                th_cfg_error(f,
+                    "Unexpected character '%c', section start '{' was expected.\n", c);
+                parseMode = PM_ERROR;
+            } else {
+                int res = th_cfg_read_sect(f, (cfgitem_t *) item->data, nesting + 1);
+                c = -1;
+                if (res > 0)
+                    validError = TRUE;
+                else if (res < 0)
+                    parseMode = PM_ERROR;
+                else {
+                    prevMode = parseMode;
+                    parseMode = PM_NORMAL;
+                }
+            }
+            break;
+            
+        case PM_STRING:
+            /* String parsing mode */
+            if (isStart) {
+                /* Start of string, get delimiter */
+                tmpCh = c;
+                isStart = FALSE;
+                strPos = 0;
+            } else if (c == tmpCh) {
+                /* End of string, set the value */
+                tmpStr[strPos] = 0;
+                
+                if (item->type == ITEM_HEX_TRIPLET) {
+                } else if (item->type == ITEM_STRING) {
+                    th_pstrcpy((char **) item->data, tmpStr);
+                }
+                
+                prevMode = parseMode;
+                parseMode = PM_NORMAL;
+            } else {
+                /* Add character to string */
+                VADDCH(c)
+                else
+                {
+                    /* Error! String too long! */
+                    th_cfg_error(f,
+                        "String too long! Maximum is %d characters.",
+                        SET_MAX_BUF);
+                    parseMode = PM_ERROR;
+                }
+            }
+
+            c = -1;
+            break;
+
+        case PM_INT:
+            /* Integer parsing mode */
+            if (isStart && item->type == ITEM_UINT && c == '-') {
+                /* Error! Negative values not allowed for unsigned ints */
+                th_cfg_error(f,
+                        "Negative value specified for %s, unsigned value expected.",
+                        item->name);
+                parseMode = PM_ERROR;
+            } else if (isStart && (c == '-' || c == '+')) {
+                VADDCH(c)
+                else
+                isError = TRUE;
+            } else if (th_isdigit(c)) {
+                VADDCH(c)
+                else
+                isError = TRUE;
+            } else if (VISEND(c)) {
+                /* End of integer parsing mode */
+                tmpStr[strPos] = 0;
+                switch (item->type) {
+                case ITEM_INT:
+                    *((int *) item->data) = atoi(tmpStr);
+                    break;
+
+                case ITEM_UINT:
+                    *((unsigned int *) item->data) = atol(tmpStr);
+                    break;
+                }
+
+                prevMode = parseMode;
+                parseMode = PM_NORMAL;
+            } else {
+                /* Error! Unexpected character. */
+                th_cfg_error(f,
+                    "Unexpected character '%c' for integer setting '%s'.",
+                    c, item->name);
+                parseMode = PM_ERROR;
+            }
+
+            if (isError) {
+                /* Error! String too long! */
+                th_cfg_error(f, "String too long! Maximum is %d characters.",
+                        SET_MAX_BUF);
+                parseMode = PM_ERROR;
+            }
+
+            isStart = FALSE;
+            c = -1;
+            break;
+
+        case PM_BOOL:
+            /* Boolean parsing mode */
+            if (isStart) {
+                tmpCh = c;
+                isStart = FALSE;
+            } else if (VISEND(c)) {
+                BOOL tmpBool;
+                
+                /* End of boolean parsing */
+                switch (tmpCh) {
+                case 'Y': case 'y':
+                case 'T': case 't':
+                case '1':
+                    tmpBool = TRUE;
+                    break;
+                
+                case 'N': case 'n':
+                case 'F': case 'f':
+                case '0':
+                    tmpBool = FALSE;
+                    break;
+                
+                default:
+                    isError = TRUE;
+                }
+                
+                if (isError) {
+                    th_cfg_error(f, "Invalid boolean value for '%s'.\n", item->name);
+                    parseMode = PM_ERROR;
+                } else {
+                    *((BOOL *) item->data) = tmpBool;
+
+                    prevMode = parseMode;
+                    parseMode = PM_NORMAL;
+                }
+            }
+            c = -1;
+            break;
+        }
+    }
+
+    /* Check for validation errors */
+    if (validError)
+        return 1;
+
+    /* Return result */
+    if (parseMode == PM_ERROR)
+        return -2;
+    else
+        return 0;
+}
+
+
+int th_cfg_read(FILE *inFile, char *filename, cfgitem_t * cfg)
+{
+    conffile_t f;
+    
+    f.file = inFile;
+    f.filename = filename;
+    f.line = 1;
+    
+    return th_cfg_read_sect(&f, cfg, 0);
+}
+
+
+/* Write a configuration into file
+ */
+static void th_print_indent(conffile_t *f, int nesting)
+{
+    int i;
+    for (i = 0; i < nesting * 2; i++)
+        fputc(' ', f->file);
+}
+
+
+static int th_cfg_write_sect(conffile_t *f, cfgitem_t *item, int nesting)
+{
+    while (item != NULL) {
+        if (item->type == ITEM_COMMENT) {
+            th_print_indent(f, nesting);
+            if (fprintf(f->file, "# %s\n", (item->name != NULL) ? item->name : "" ) < 0)
+                return -1;
+        } else
+        if (item->name != NULL) {
+            th_print_indent(f, nesting);
+            
+            switch (item->type) {
+            case ITEM_STRING:
+                if (*((char **) item->data) == NULL) {
+                    if (fprintf(f->file, "#%s = \"\"\n", item->name) < 0)
+                        return -3;
+                } else {
+                    if (fprintf(f->file, "%s = \"%s\"\n",
+                        item->name, *((char **) item->data)) < 0)
+                        return -3;
+                }
+                break;
+
+            case ITEM_INT:
+                if (fprintf(f->file, "%s = %i\n",
+                    item->name, *((int *) item->data)) < 0)
+                    return -4;
+                break;
+
+            case ITEM_UINT:
+                if (fprintf(f->file, "%s = %d\n",
+                    item->name, *((unsigned int *) item->data)) < 0)
+                    return -5;
+                break;
+
+            case ITEM_BOOL:
+                if (fprintf(f->file, "%s = %s\n",
+                    item->name, *((BOOL *) item->data) ? "yes" : "no") < 0)
+                    return -6;
+                break;
+            
+            case ITEM_SECTION:
+                {
+                int res;
+                if (fprintf(f->file, "\n%s = {\n", item->name) < 0)
+                    return -7;
+                res = th_cfg_write_sect(f, (cfgitem_t *) item->data, nesting + 1);
+                if (res != 0) return res;
+                if (fprintf(f->file, "} # End of '%s'\n\n", item->name) < 0)
+                    return -8;
+                }
+                break;
+            
+            case ITEM_HEX_TRIPLET:
+                if (fprintf(f->file, "%s = \"%06x\"\n",
+                    item->name, *((int *) item->data)) < 0)
+                    return -6;
+                break;
+            }
+        }
+        item = item->next;
+    }
+    
+    return 0;
+}
+
+
+int th_cfg_write(FILE *outFile, char *filename, cfgitem_t *cfg)
+{
+    conffile_t f;
+    
+    if (cfg == NULL)
+        return -1;
+
+    f.file = outFile;
+    f.filename = filename;
+    f.line = 1;
+    
+    fprintf(outFile, "# Configuration written by %s %s\n\n", 
+        th_prog_fullname, th_prog_version);
+    
+    return th_cfg_write_sect(&f, cfg, 0);
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/th_config.h	Sat Oct 30 20:37:09 2010 +0300
@@ -0,0 +1,68 @@
+/*
+ * Very simple configuration file handling
+ * Programmed and designed by Matti 'ccr' Hamalainen
+ * (C) Copyright 2004-2008 Tecnic Software productions (TNSP)
+ *
+ * Please read file 'COPYING' for information on license and distribution.
+ */
+#ifndef _TH_CONFIG_H
+#define _TH_CONFIG_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "th_util.h"
+#include <stdio.h>
+
+
+/* Definitions
+ */
+enum ITEM_TYPE {
+    ITEM_SECTION = 1,
+    ITEM_COMMENT,
+    ITEM_STRING,
+    ITEM_INT,
+    ITEM_UINT,
+    ITEM_BOOL,
+    ITEM_FLOAT,
+    ITEM_HEX_TRIPLET
+};
+
+
+typedef struct _cfgitem_t {
+    int  type;
+    char *name;
+    union {
+        void *data;
+        int *val_int;
+        unsigned int *val_uint;
+        char *val_str;
+        BOOL *val_bool;
+        struct _cfgitem_t *section;
+    };
+    
+    struct _cfgitem_t *next, *prev;
+} cfgitem_t;
+
+
+/* Functions
+ */
+int     th_cfg_read(FILE *, char *, cfgitem_t *);
+void    th_cfg_free(cfgitem_t *);
+int     th_cfg_write(FILE *, char *, cfgitem_t *);
+
+int     th_cfg_add_section(cfgitem_t **cfg, char *name, cfgitem_t *data);
+int     th_cfg_add_comment(cfgitem_t **cfg, char *comment);
+int     th_cfg_add_int(cfgitem_t **cfg, char *name, int *data, int itemDef);
+int     th_cfg_add_uint(cfgitem_t **cfg, char *name, unsigned int *data, unsigned int itemDef);
+int     th_cfg_add_string(cfgitem_t **cfg, char *name, char **data, char *itemDef);
+int     th_cfg_add_bool(cfgitem_t **cfg, char *name, BOOL *data, BOOL itemDef);
+int     th_cfg_add_float(cfgitem_t **cfg, char *name, float *data, float itemDef);
+int     th_cfg_add_hexvalue(cfgitem_t **cfg, char *name, int *data, int itemDef);
+
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _TH_CONFIG_H */