A simple .ini file interface.

Dependents:   Smart-WiFly-WebServer SignalGenerator WattEye X10Svr

IniManager.cpp

Committer:
WiredHome
Date:
2014-03-26
Revision:
5:bfeb0882bd82
Parent:
4:70042853d43b
Child:
7:60f5dc3467ff

File content as of revision 5:bfeb0882bd82:

// Simple INI file manager.
//
#ifdef WIN32
#include "string.h"
#include "stdlib.h"
#include "stdio.h"
#else
#include "mbed.h"
#endif

#include "IniManager.h"

//#define DEBUG       //Debug is disabled by default

#if (defined(DEBUG) && !defined(TARGET_LPC11U24))
#define DBG(x, ...)  std::printf("[DBG INI%4d] "x"\r\n", __LINE__, ##__VA_ARGS__);
#define WARN(x, ...) std::printf("[WRN INI%4d] "x"\r\n", __LINE__, ##__VA_ARGS__);
#define ERR(x, ...)  std::printf("[ERR INI%4d] "x"\r\n", __LINE__, ##__VA_ARGS__);
#define INFO(x, ...) std::printf("[INF INI%4d] "x"\r\n", __LINE__, ##__VA_ARGS__);
#else
#define DBG(x, ...)
#define WARN(x, ...)
#define ERR(x, ...)
#define INFO(x, ...)
#endif

INI::INI(const char * file)
    : iniFile(0)
{
    if (file) {
        iniFile = (char *)malloc(strlen(file)+1);
        if (iniFile)
            strcpy(iniFile, file);
        else
            iniFile = NULL;
    }
}


INI::~INI(void)
{
    if (iniFile)
        free(iniFile);
}


bool INI::SetFile(const char * file)
{
    if (file) {
        if (iniFile)
            free(iniFile);
        iniFile = (char *)malloc(strlen(file)+1);
        if (iniFile) {
            strcpy(iniFile, file);
            return true;
        }
        else
            iniFile = NULL;        
    }
    return false;
}

bool INI::ReadString(const char * section, const char * key, char * buffer, size_t bufferSize, const char * defaultString)
{
    bool found = false;
    if (!iniFile)
        return found;
    CrashRecover();
    INFO("ReadString from %s\r\n", iniFile);
    FILE * fp = fopen(iniFile,"rt");
    if (fp) {
        char buf[INTERNAL_BUF_SIZE];
        bool inSection = (section == NULL) ? true : false;

        while(fgets(buf, sizeof(buf), fp)) {
            int x = strlen(buf) - 1;        // remove trailing \r\n combinations
            while (x >= 0 && buf[x] < ' ')
                buf[x--] = '\0';
            INFO("read in [%s]\r\n", buf);
            if (inSection && buf[0] != '[') {
                char * eq = strchr(buf, '=');
                if (eq) {
                    *eq++ = '\0';
                    if ( (strcmp(buf,key) == 0) && (strlen(eq) <= bufferSize) ) {
                        strcpy(buffer, eq);
                        memset(buf, 0, INTERNAL_BUF_SIZE);  // secure the memory space
                        found = true;
                        break;
                    }
                }
            } else {
                if (buf[0] == '[') {
                    char * br = strchr(buf, ']');
                    inSection = false;
                    if (br) {
                        *br = '\0';
                        if (strcmp(buf+1, section) == 0)
                            inSection = true;
                    }
                }
            }
        }
        fclose(fp);
    }
    if (!found && defaultString != NULL && *defaultString) {
        strncpy(buffer, defaultString, bufferSize);
        buffer[bufferSize-1] = '\0';
        INFO("sub %s.\r\n", buffer);
        found = true;
    }
    return found;
}

bool INI::CrashRecover()
{
    char * newFile = (char *)malloc(strlen(iniFile)+1);
    char * bakFile = (char *)malloc(strlen(iniFile)+1);

    if (newFile && bakFile) {
        WARN("*** CrashRecover\r\n");
        strcpy(bakFile, iniFile);
        strcpy(newFile, iniFile);
        strcpy(bakFile + strlen(bakFile) - 4, ".bak");
        strcpy(newFile + strlen(newFile) - 4, ".new");

        FILE * repair = fopen(newFile, "rt");
        if (repair) {
            int i;
            i = i;    // suppress warning about i not used when !DEBUG
            // helps recover if the system crashed before it could swap in the new file
            INFO("*** repairing\r\n");
            fclose(repair);
            i = remove(bakFile);            // remove an old .bak
            INFO("remove(%s) returned %d\r\n", bakFile, i);
            i = Rename(iniFile, bakFile);   // move the existing .ini to .bak
            INFO("rename(%s,%s) returned %d\r\n", iniFile, bakFile, i);
            i = Rename(newFile, iniFile);   // move the new .new to .ini
            INFO("rename(%s,%s) returned %d\r\n", newFile, iniFile, i);
        }
    }
    free(newFile);
    free(bakFile);
    return true;
}

// Create the new version as .new
// once complete, if something actually changed, then rename the .ini to .bak and rename the .new to .ini
// once complete, if nothing actually changed, then delete the .new
//
bool INI::WriteString(const char * section, const char * key, char * value)
{
    bool found = false;
    bool fileChanged = false;

    if (!iniFile || (value != NULL && strlen(value) > INTERNAL_BUF_SIZE))
        return found;

    char * newFile = (char *)malloc(strlen(iniFile)+1);
    char * bakFile = (char *)malloc(strlen(iniFile)+1);
    if (!newFile)
        return found;       // no memory
    if (!bakFile) {
        free(newFile);
        return found;
    }
    strcpy(bakFile, iniFile);
    strcpy(newFile, iniFile);
    strcpy(bakFile + strlen(bakFile) - 4, ".bak");
    strcpy(newFile + strlen(newFile) - 4, ".new");

    CrashRecover();

    INFO("Opening [%s] and [%s]\r\n", iniFile, newFile);
    FILE * fi = fopen(iniFile, "rt");
    FILE * fo = fopen(newFile, "wt");
    if (fo) {
        char buf[INTERNAL_BUF_SIZE];
        bool inSection = (section == NULL) ? true : false;

        if (fi) {
            while(fgets(buf, sizeof(buf), fi)) {
                // if not inSection, copy across
                // if inSection and not key, copy across
                // if InSection and key, write new value (or skip if value is null)
                int x = strlen(buf) - 1;        // remove trailing \r\n combinations
                while (x >= 0 && buf[x] < ' ')
                    buf[x--] = '\0';
                if (inSection && buf[0] != '[') {
                    char * eq = strchr(buf, '=');
                    if (eq) {
                        *eq++ = '\0';
                        if (strcmp(buf,key) == 0) {
                            if (value != NULL && strcmp(eq, value) != 0) {
                                // replace the old record
                                if (value != NULL) {
                                    fprintf(fo, "%s=%s\n", key, value);
                                    printf("write: %s=%s\r\n", key, value);
                                }
                            }
                            fileChanged = true;
                            inSection = false;
                            found = true;
                        } else {
                            // write old record
                            fprintf(fo, "%s=%s\n", buf, eq);
                            INFO("write: %s=%s\r\n", buf, eq);
                        }
                    } else {
                        // what to do with unknown record(s)?
                        // fprintf(fo, "%s\n", buf);    // eliminate them
                    }
                } else {
                    if (buf[0] == '[') {
                        char * br = strchr(buf, ']');
                        if (inSection) { // found next section while in good section
                            // Append new record to desired section
                            if (value != NULL) {
                                fprintf(fo, "%s=%s\r\n", key, value);
                                INFO("write: %s=%s\r\n", key, value);
                                fileChanged = true;
                            }
                            found = true;
                        }
                        inSection = false;
                        // write old record
                        fprintf(fo, "%s\r\n", buf);
                        INFO("write: %s\r\n", buf);
                        if (br) {
                            *br = '\0';
                            if (strcmp(buf+1, section) == 0)
                                inSection = true;
                        }
                    } else {
                        // copy unaltered records across
                        if (buf[0]) {
                            fprintf(fo, "%s\r\n", buf);
                            INFO("write: %s\r\n", buf);
                        }
                    }
                }
            }
            INFO("close %s\r\n", iniFile);
            fclose(fi);
        }
        if (!found) {
            // No old file, just create it now
            if (value != NULL) {
                if (!inSection) {
                    fprintf(fo, "[%s]\r\n", section);
                    INFO("write: [%s]\r\n", section);
                }
                fprintf(fo, "%s=%s\r\n", key, value);
                INFO("write: %s=%s\r\n", key, value);
                fileChanged = true;
            }
            found = true;
        }
        INFO("close %s\r\n", newFile);
        fclose(fo);
    }
    if (fileChanged) {
        INFO("remove bak, rename ini to bak, rename new to ini\r\n");
        remove(bakFile);            // remove an old .bak
        Rename(iniFile, bakFile);   // move the existing .ini to .bak
        Rename(newFile, iniFile);   // move the new .new to .ini
        #ifdef RTOS_H
        Thread::wait(1000);
        #else
        wait(1);
        #endif
    }
    free(newFile);
    free(bakFile);
    return found;
}


//***********************************************************
// Private version that also works with local file system
//    Returns -1 = error; 0 = success
//***********************************************************
int INI::Rename(const char *oldfname, const char *newfname)
{
    int retval = 0;
    int ch;

    FILE *fpold = fopen(oldfname, "r");   // src file
    FILE *fpnew = fopen(newfname, "w");   // dest file

    while (1) {                   // Copy src to dest
        ch = fgetc(fpold);        // until src EOF read.
        if (ch == EOF) break;
        fputc(ch, fpnew);
    }

    fclose(fpnew);
    fclose(fpold);

    fpnew = fopen(newfname, "r"); // Reopen dest to insure
    if(fpnew == NULL) {           // that it was created.
        retval = (-1);            // Return Error.
    } else {
        fclose(fpnew);
        remove(oldfname);         // Remove original file.
        retval = (0);             // Return Success.
    }
    return (retval);
}

//***********************************************************
// Private version that also works with local file system
//            Returns -1 = error; 0 = success
//***********************************************************
int INI::Copy(const char *src, const char *dst)
{
    int retval = 0;
    int ch;

    FILE *fpsrc = fopen(src, "r");   // src file
    FILE *fpdst = fopen(dst, "w");   // dest file

    while (1) {                  // Copy src to dest
        ch = fgetc(fpsrc);       // until src EOF read.
        if (ch == EOF) break;
        fputc(ch, fpdst);
    }
    fclose(fpsrc);
    fclose(fpdst);

    fpdst = fopen(dst, "r");     // Reopen dest to insure
    if(fpdst == NULL) {          // that it was created.
        retval = (-1);           // Return error.
    } else {
        fclose(fpdst);
        retval = (0);            // Return success.
    }
    return (retval);
}


#if 0
// Test code for basic regression testing
//
#include <stdio.h>
#include <assert.h>
#include <string.h>

#include "INI.h"

#define TESTFILE "test.ini"

int main(int argc, char * argv[])
{
    FILE * fp;
    char buffer[100];
    INI ini(TESTFILE);

    // Start testing
    _unlink(TESTFILE);
    assert(ini.ReadString("Section 1", "Name 1", buffer, sizeof(buffer)) == false);

    fp = fopen(TESTFILE, "wt");
    assert(fp);
    fprintf(fp, "[Section 1]\n");
    fprintf(fp, "Name 1=Value 1\n");
    fprintf(fp, "Name 2=Value 2\n");
    fprintf(fp, "\n");
    fprintf(fp, "[Section 2]\n");
    fprintf(fp, "Name 1=Value 2\n");
    fprintf(fp, "Name 2=Value 2\n");
    fprintf(fp, "Name 3=Value 3\n");
    fprintf(fp, "\n");
    fclose(fp);

    assert(ini.ReadString("Section 2", "Name 2", buffer, sizeof(buffer)) == true);
    assert(strcmp("Value 2", buffer) == 0);

    assert(ini.ReadString("Section 3", "Name", buffer, sizeof(buffer)) == false);
    assert(ini.ReadString("Section 1", "Name 3", buffer, sizeof(buffer)) == false);

    assert(ini.WriteString("Section 1", "Name 4", "Value 4") == true);
    assert(ini.ReadString("Section 1", "Name 2", buffer, sizeof(buffer)) == true);
    assert(ini.ReadString("Section 1", "Name 3", buffer, sizeof(buffer)) == false);
    assert(ini.ReadString("Section 1", "Name 4", buffer, sizeof(buffer)) == true);
    assert(strcmp("Value 4", buffer) == 0);

    assert(ini.WriteString("Section 1", "Name 4", NULL) == true);
    assert(ini.ReadString("Section 1", "Name 4", buffer, sizeof(buffer)) == false);

    return 0;
}
#endif