config.cpp

Tue, 19 Mar 2013 15:29:42 +0200

author
Santeri Piippo <crimsondusk64@gmail.com>
date
Tue, 19 Mar 2013 15:29:42 +0200
changeset 30
31ff9aabd506
parent 29
55406ce7446e
child 68
c637b172d565
permissions
-rw-r--r--

Licensed LDForge GPL3, added some more icons

/*
 *  LDForge: LDraw parts authoring CAD
 *  Copyright (C) 2013 Santeri `arezey` Piippo
 *  
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
// #include <stdlib.h>
#include <errno.h>
#include <time.h>
#include "str.h"
#include "config.h"

#ifdef CONFIG_WITH_QT
#include <QDir>
#endif // CONFIG_WITH_QT

// =============================================================================
// Define the configs
#define CFG(TYPE, SECT, NAME, DESCR, DEFAULT) \
	TYPE##config SECT##_##NAME (CFGSECTNAME (SECT), DESCR, \
		DEFAULT, #NAME, #SECT "_" #NAME, #TYPE, #DEFAULT);

#define SECT(...)
 #include "cfgdef.h"
#undef CFG
#undef SECT

// =============================================================================
config* config::pointers[] = {
#define CFG(TYPE, SECT, NAME, DESCR, DEFAULT) &SECT##_##NAME,
#define SECT(...)
 #include "cfgdef.h"
#undef CFG
#undef SECT
};

// =============================================================================
const char* config::sections[] = {
#define CFG(...)
#define SECT(A,B) #A,
 #include "cfgdef.h"
#undef CFG
#undef SECT
};

// =============================================================================
const char* config::sectionNames[] = {
#define CFG(...)
#define SECT(A,B) #B,
 #include "cfgdef.h"
#undef CFG
#undef SECT
};

// =============================================================================
const char* g_WeekdayNames[7] = {
	"Sunday",
	"Monday",
	"Tuesday",
	"Wednesday",
	"Thursday",
	"Friday",
	"Saturday",
};

// =============================================================================
static const char* g_MonthNames[12] = {
	"Januray",
	"February",
	"March",
	"April",
	"May",
	"June",
	"July",
	"August",
	"September",
	"October",
	"November"
	"December",
};

static const char* g_ConfigTypeNames[] = {
	"None",
	"Integer",
	"String",
	"Float",
	"Boolean",
};

// =============================================================================
// Load the configuration from file
bool config::load () {
	FILE* fp = fopen (filepath().chars(), "r");
	char linedata[MAX_INI_LINE];
	char* line;
	size_t ln = 0;
	configsection_e section = NO_CONFIG_SECTION;
	
	if (!fp)
		return false; // can't open for reading
	
	// Read the values.
	while (fgets (linedata, MAX_INI_LINE, fp)) {
		ln++;
		line = linedata;
		
		while (*line != 0 && (*line <= 32 || *line >= 127))
			line++; // Skip junk
		
		if (*line == '\0' || line[0] == '#')
			continue; // Empty line or comment.
		
		if (line[0] == '[') {
			// Section
			char* endbracket = strchr (line, ']');
			
			if (!endbracket) {
				fprintf (stderr, "badly formed section: %s", line);
				continue;
			}
			
			str sectionName = str (line).substr (1, endbracket - line);
			const configsection_e oldsection = section;
			section = NO_CONFIG_SECTION;
			
			// Find the section
			for (unsigned i = 0; i < NUM_ConfigSections && section == NO_CONFIG_SECTION; i++)
				if (sectionName.compare (sectionNames[i]) == 0)
					section = (configsection_e)i;
			
			if (section == NO_CONFIG_SECTION) {
				fprintf (stderr, "unknown config section `%s`\n", sectionName.chars());
				section = oldsection;
			}
			
			continue;
		}
		
		// Find the equals sign.
		char* equals = strchr (line, '=');
		if (!equals) {
			fprintf (stderr, "couldn't find `=` sign in entry `%s`\n", line);
			continue;
		}
		
		str entry = str (line).substr (0, equals - line);
		
		str configname;
		configname.format ("%s_%s", sections[section], entry.chars ());
		
		// Find the config entry for this.
		config* cfg = NULL;
		for (size_t i = 0; i < NUM_CONFIG && !cfg; i++)
			if (configname.compare (pointers[i]->fullname) == 0)
				cfg = pointers[i];
		
		if (!cfg) {
			fprintf (stderr, "unknown config `%s`\n", configname.chars());
			continue;
		}
		
		str valstring = str (line).substr (equals - line + 1, -1);
		
		// Trim the crap off the end
		while (~valstring) {
			char c = valstring[~valstring - 1];
			if (c <= 32 || c >= 127)
				valstring -= 1;
			else
				break;
		}
		
		switch (const_cast<config*> (cfg)->getType()) {
		case CONFIG_int:
			static_cast<intconfig*> (cfg)->value = atoi (valstring.chars());
			break;
		case CONFIG_str:
			static_cast<strconfig*> (cfg)->value = valstring;
			break;
		case CONFIG_float:
			static_cast<floatconfig*> (cfg)->value = atof (valstring.chars());
			break;
		case CONFIG_bool:
		{
			bool& val = static_cast<boolconfig*> (cfg)->value;
			
			if (+valstring == "TRUE" || valstring == "1")
				val = true;
			else if (+valstring == "FALSE" || valstring == "0")
				val = false;
			break;
		}
		default:
			break;
		}
	}
	
	fclose (fp);
	return true;
}

// =============================================================================
// Write a given formatted string to the given file stream
static size_t writef (FILE* fp, const char* fmt, ...) {
	va_list va;
	
	va_start (va, fmt);
	char* buf = vdynformat (fmt, va, 256);
	va_end (va);
	
	size_t len = fwrite (buf, 1, strlen (buf), fp);
	delete[] buf;
	
	return len;
}

// =============================================================================
// Save the configuration to disk
bool config::save () {
#ifdef APPNAME
	#ifdef CONFIG_WITH_QT
		// If the directory doesn't exist, create it now.
		if (!QDir (dirpath().chars()).exists ()) {
			fprintf (stderr, "Creating config path %s...\n", dirpath().chars());
			if (!QDir ().mkpath (dirpath().chars())) {
				fprintf (stderr, "Failed to create the directory. Configuration cannot be saved!\n");
				return false; // Couldn't create directory
			}
		}
	#else
		#warning Need QT to check for directories. Config will not be able
		#warning to save properly if ~/.APPNAME/ does not exist.
	#endif // CONFIG_WITH_QT
#endif // CONFIG_DIRECTORY
	
	FILE* fp = fopen (filepath().chars(), "w");
	printf ("writing cfg to %s\n", filepath().chars());
	
	if (!fp) {
		printf ("Couldn't open %s for writing\n", filepath().chars());
		return false;
	}
	
	const time_t curtime = time (NULL);
	const struct tm* timeinfo = localtime (&curtime);
	const char* daysuffix =
		(timeinfo->tm_mday % 10 == 1) ? "st" :
		(timeinfo->tm_mday % 10 == 2) ? "nd" :
		(timeinfo->tm_mday % 10 == 3) ? "rd" : "th";
	
	writef (fp, "# Configuration file for " APPNAME "\n");
	writef (fp, "# Written on %s, %s %d%s %d %.2d:%.2d:%.2d\n",
		g_WeekdayNames[timeinfo->tm_wday], g_MonthNames[timeinfo->tm_mon],
		timeinfo->tm_mday, daysuffix, timeinfo->tm_year + 1900,
		timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);
	writef (fp, "\n");
	
	for (int i = 0; i < NUM_ConfigSections; i++) {
		if (i > 0)
			writef (fp, "\n");
		
		writef (fp, "[%s]\n", sectionNames[i]);
		bool first = true;
		
		for (size_t j = 0; j < NUM_CONFIG; j++) {
			config* cfg = pointers[j];
			
			if (cfg->sect != i)
				continue;
			
			if (!first)
				writef (fp, "\n");
			
			str valstring;
			switch (cfg->getType()) {
			case CONFIG_int:
				valstring.format ("%d", static_cast<intconfig*> (cfg)->value);
				break;
			case CONFIG_str:
				valstring = static_cast<strconfig*> (cfg)->value;
				break;
			case CONFIG_float:
				valstring.format ("%f", static_cast<floatconfig*> (cfg)->value);
				
				// Trim any trailing zeros
				if (valstring.first (".") != -1) {
					while (valstring[~valstring - 1] == '0')
						valstring -= 1;
					
					// But don't trim the only one out...
					if (valstring[~valstring - 1] == '.')
						valstring += '0';
				}
				
				break;
			case CONFIG_bool:
				valstring = (static_cast<boolconfig*> (cfg)->value) ? "true" : "false";
				break;
			default:
				break;
			}
			
			// Write the entry now.
			writef (fp, "# %s, default: %s\n", g_ConfigTypeNames[cfg->getType()], cfg->defaultstring);
			writef (fp, "# %s: %s\n", cfg->fullname, cfg->description);
			writef (fp, "%s=%s\n", cfg->name, valstring.chars());
			first = false;
		}
	}
	
	fclose (fp);
	return true;
}

// =============================================================================
void config::reset () {
	for (size_t i = 0; i < NUM_CONFIG; i++)
		pointers[i]->resetValue ();
}

// =============================================================================
str config::filepath () {
#ifdef APPNAME
	str path;
	path.format ("%s" APPNAME ".ini", dirpath().chars());
	return path;
#else // APPNAME
	return "config.ini";
#endif // APPNAME
}

// =============================================================================
str config::dirpath () {
#ifdef APPNAME
	str path;
	
	#ifdef CONFIG_WITH_QT
		path = (QDir::homePath ().toStdString().c_str());
	#else // CONFIG_WITH_QT
		path = "~";
	#endif // CONFIG_WITH_QT
	
	path += "/." APPNAME "/";
#else
	path = "./";
#endif // APPNAME
	
	return path;
}

mercurial