A simple .ini file interface.

Dependents:   Smart-WiFly-WebServer SignalGenerator WattEye X10Svr

Files at this revision

API Documentation at this revision

Comitter:
WiredHome
Date:
Mon Aug 12 22:57:54 2013 +0000
Child:
1:1e2ee9bbee40
Commit message:
A simple ini file interface.

Changed in this revision

IniManager.cpp Show annotated file Show diff for this revision Revisions of this file
IniManager.h Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/IniManager.cpp	Mon Aug 12 22:57:54 2013 +0000
@@ -0,0 +1,361 @@
+// Simple INI file manager.
+//
+#ifdef WIN32
+#include "string.h"
+#include "stdlib.h"
+#include "stdio.h"
+#else
+#include "mbed.h"
+#endif
+
+#include "IniManager.h"
+
+INI::INI(const char * file)
+    : iniFile(0)
+{
+    if (file) {
+        iniFile = (char *)malloc(strlen(file)+1);
+        if (iniFile)
+            strcpy(iniFile, file);
+    }
+    FILE * fo = fopen("/local/test.txt", "wt");
+    if (fo) {
+        printf("Writing to /local/test.txt\r\n");
+        fprintf(fo, "This is three - %d\r\n", 3);
+        fclose(fo);
+    }
+}
+
+
+INI::~INI(void)
+{
+    if (iniFile)
+        free(iniFile);
+}
+
+
+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();
+    printf("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';
+            printf("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';
+        printf("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) {
+        printf("*** 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) {
+            // helps recover if the system crashed before it could swap in the new file
+            printf("*** repairing\r\n");
+            fclose(repair);
+            int i;
+            i = remove(bakFile);            // remove an old .bak
+            printf("remove(%s) returned %d\r\n", bakFile, i);
+            i = Rename(iniFile, bakFile);   // move the existing .ini to .bak
+            printf("rename(%s,%s) returned %d\r\n", iniFile, bakFile, i);
+            i = Rename(newFile, iniFile);   // move the new .new to .ini
+            printf("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();
+
+    printf("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);
+                            printf("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);
+                                printf("write: %s=%s\r\n", key, value);
+                                fileChanged = true;
+                            }
+                            found = true;
+                        }
+                        inSection = false;
+                        // write old record
+                        fprintf(fo, "%s\r\n", buf);
+                        printf("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);
+                            printf("write: %s\r\n", buf);
+                        }
+                    }
+                }
+            }
+            printf("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);
+                    printf("write: [%s]\r\n", section);
+                }
+                fprintf(fo, "%s=%s\r\n", key, value);
+                printf("write: %s=%s\r\n", key, value);
+                fileChanged = true;
+            }
+            found = true;
+        }
+        printf("close %s\r\n", newFile);
+        fclose(fo);
+    }
+    if (fileChanged) {
+        printf("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
+        wait(1);
+    }
+    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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/IniManager.h	Mon Aug 12 22:57:54 2013 +0000
@@ -0,0 +1,108 @@
+
+#ifndef INIMANAGER_H
+#define INIMANAGER_H
+
+#define INTERNAL_BUF_SIZE 250
+
+/** A simple ini file manager.
+*
+* This is a simple ini file manager intended for low duty cycle usage. 
+*
+* It follows an old "Windows" style of ini file format with section, key, and value.
+* This version only operates on strings at this time.
+*
+* As a "simple" ini file manager, this version does not cache anything internally.
+* This comes at the "cost" that each write transaction will read and replace the
+* ini file. Read transactions will open and scan the file.
+*
+* Also, an internal stack-frame buffer is used to manage the read operations. As
+* such, no single record in the file can exceed this buffer size (compile time set
+* with a default of 250 bytes). A single record for a section is surrounded with
+* '[' and ']' and a new line appended. A single record for an entry within a
+* section for a key, value pair is separated with an '=' and a new line appended.
+* @code
+* [section name]
+* Key name=value for Key name
+* Another key=another value
+* @endcode
+*/
+class INI
+{
+public:
+    /** constructor accepts a filename
+    *
+    * @param file is the filename to manage. Memory is allocated to hold
+    *       a private copy of the filename. Be sure that this parameter
+    *       has the right path prefix based on what file system you have.
+    */
+    INI(const char * file);
+
+    /** destructor for the ini manager.
+    *
+    * releases the memory allocation.
+    */
+    ~INI(void);
+
+    /** Read a string from the ini file - if it exists.
+    *
+    * This searches the ini file for the named section and key and if found it will
+    * return the string associated with that entry into a user supplied buffer.
+    * 
+    * @param section is the name of the section to search.
+    * @param key is the name of the key to search.
+    * @param buffer is the caller provided buffer for this method to put the string into.
+    * @param bufferSize is the caller provided declaration of the available space.
+    * @param defaultString is an optional parameter that sets the buffer if the section/key is not found.
+    * 
+    * @return true if the section, key, and value are found AND the value will fit in the buffer
+    *       in which case it is written into the buffer; false otherwise.
+    */
+    bool ReadString(const char * section, const char * key, char * buffer, size_t bufferSize, const char * defaultString = NULL);
+
+    /** Writes a string into the ini file
+    *
+    * This writes a given string into an ini file in the named section and key.
+    * 
+    * @param section is the name of the section to search.
+    * @param key is the name of the key to search.
+    * @param buffer is the caller provided buffer containing the string to write. If
+    *       buffer is NULL, then any existing entry is removed.
+    *
+    * @return true if the write was successful; false otherwise.
+    */
+    bool WriteString(const char * section, const char * key, char * buffer);
+
+private:
+    char * iniFile;
+
+    /** Crash recover if we can.
+    *
+    * This will attempt to crash recover. If while writing a new file, it could not
+    * complete the process, it may be able to complete the process on the next access.
+    *
+    * @return true, always until I find a reason not to.
+    */
+    bool CrashRecover();
+    
+    /** Rename a file
+    *
+    * This version also works on the local file system.
+    *
+    * @param oldfname is the old file name
+    * @param newfname is the new file name
+    * @returns 0 on success, -1 on error
+    */
+    int Rename(const char *oldfname, const char *newfname);
+    
+    /** Copy a file
+    *
+    * This version also works on the local file system.
+    *
+    * @param src is the source file
+    * @param dst is the destination file
+    * @returns 0 on success, -1 on error
+    */
+    int Copy(const char *src, const char *dst);
+};
+
+#endif // INIMANAGER_H