A simple .ini file interface.
Dependents: Smart-WiFly-WebServer SignalGenerator WattEye X10Svr
Revision 0:ae5bf432c249, committed 2013-08-12
- 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