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