/** \file wpref.c * Handle loading and saving preferences. */ /* XTrkCad - Model Railroad CAD * Copyright (C) 2005 Dave Bullis * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #define GTK_DISABLE_SINGLE_INCLUDES #define GDK_DISABLE_DEPRECATED #define GTK_DISABLE_DEPRECATED #define GSEAL_ENABLE #include #include #include "wlib.h" #include "gtkint.h" #include "dynarr.h" #include "i18n.h" #include "xtrkcad-config.h" extern char wConfigName[]; static char appLibDir[BUFSIZ]; static char appWorkDir[BUFSIZ]; static char userHomeDir[BUFSIZ]; static char *profileFile; /* ******************************************************************************* * * Get Dir Names * ******************************************************************************* */ /** Find the directory where configuration files, help, demos etc are installed. * The search order is: * 1. Directory specified by the XTRKCADLIB environment variable * 2. Directory specified by XTRKCAD_INSTALL_PREFIX/share/xtrkcad * 3. /usr/share/xtrkcad * 4. /usr/local/share/xtrkcad * * \return pointer to directory name */ const char * wGetAppLibDir( void ) { char * cp, *ep; char msg[BUFSIZ*2]; char envvar[80]; struct stat buf; if (appLibDir[0] != '\0') { return appLibDir; } for (cp=wlibGetAppName(),ep=envvar; *cp; cp++,ep++) { *ep = toupper(*cp); } #ifndef __APPLE__ if ( strstr( XTRKCAD_VERSION, "Beta" ) != NULL ) { strcat( ep, "BETA" ); } #endif strcat( ep, "LIB" ); ep = getenv( envvar ); if (ep != NULL) { if ((stat( ep, &buf) == 0 ) && S_ISDIR( buf.st_mode)) { strncpy( appLibDir, ep, sizeof(appLibDir) -1 ); //printf( "wAppLbDir=%s\n", appLibDir ); return appLibDir; } } strcpy(appLibDir, "../share/"); strcat(appLibDir, wlibGetAppName()); if ((stat( appLibDir, &buf) == 0 ) && S_ISDIR( buf.st_mode)) { //printf( "wAppLbDir=%s\n", appLibDir ); return appLibDir; } char * dir1 = "/usr/share/"; char * dir2 = "/usr/local/share/"; char * beta = ""; if ( strstr( XTRKCAD_VERSION, "Beta" ) != NULL ) { dir1 = "/usr/local/share/"; dir2 = "/usr/share/"; #ifndef __APPLE__ beta = "-beta"; #endif } strcpy( appLibDir, dir1 ); strcat( appLibDir, wlibGetAppName() ); strcat( appLibDir, beta ); if ((stat( appLibDir, &buf) == 0 ) && S_ISDIR( buf.st_mode)) { //printf( "wAppLbDir=%s\n", appLibDir ); return appLibDir; } strcpy( appLibDir, dir2 ); strcat( appLibDir, wlibGetAppName() ); if ((stat( appLibDir, &buf) == 0 ) && S_ISDIR( buf.st_mode)) { //printf( "wAppLbDir=%s\n", appLibDir ); return appLibDir; } sprintf( msg, _("The required configuration files could not be located in the expected location.\n\n" "Usually this is an installation problem. Make sure that these files are installed in either \n" " ../share/xtrkcad or\n" " /usr/share/%s or\n" " /usr/local/share/%s\n" "If this is not possible, the environment variable %s must contain " "the name of the correct directory."), wlibGetAppName(), wlibGetAppName(), envvar ); wNoticeEx( NT_ERROR, msg, _("Ok"), NULL ); appLibDir[0] = '\0'; wExit(0); return NULL; } /** * Get the working directory for the application. This directory is used for storing * internal files including rc files. If it doesn't exist, the directory is created * silently. * * \return pointer to the working directory */ const char * wGetAppWorkDir( void ) { char tmp[BUFSIZ+20]; char * homeDir; DIR *dirp; if (appWorkDir[0] != '\0') { return appWorkDir; } if ((homeDir = getenv( "HOME" )) == NULL) { wNoticeEx( NT_ERROR, _("HOME is not set"), _("Exit"), NULL); wExit(0); } sprintf( appWorkDir, "%s/.%s", homeDir, wlibGetAppName() ); #ifndef __APPLE__ if ( strstr( XTRKCAD_VERSION, "Beta" ) != NULL ) { strcat( appWorkDir, "-beta" ); } #endif if ( (dirp = opendir(appWorkDir)) != NULL ) { closedir(dirp); } else { if ( mkdir( appWorkDir, 0777 ) == -1 ) { sprintf( tmp, _("Cannot create %s"), appWorkDir ); wNoticeEx( NT_ERROR, tmp, _("Exit"), NULL ); wExit(0); } else { /* * check for default configuration file and copy to * the workdir if it exists */ struct stat stFileInfo; char appEtcConfig[BUFSIZ]; sprintf( appEtcConfig, "/etc/%s.rc", wlibGetAppName()); if ( stat( appEtcConfig, &stFileInfo ) == 0 ) { char copyConfigCmd[(BUFSIZ * 2) + 3]; sprintf( copyConfigCmd, "cp %s %s", appEtcConfig, appWorkDir ); system( copyConfigCmd ); } } } return appWorkDir; } /** * Get the user's home directory. The environment variable HOME is * assumed to contain the proper directory. * * \return pointer to the user's home directory */ const char *wGetUserHomeDir( void ) { char *homeDir; if( userHomeDir[ 0 ] != '\0' ) { return userHomeDir; } if ((homeDir = getenv( "HOME" )) == NULL) { wNoticeEx( NT_ERROR, _("HOME is not set"), _("Exit"), NULL); wExit(0); } else { strcpy( userHomeDir, homeDir ); } return userHomeDir; } /* ******************************************************************************* * * Preferences * ******************************************************************************* */ typedef struct { char * section; char * name; wBool_t present; wBool_t dirty; char * val; } prefs_t; dynArr_t prefs_da; #define prefs(N) DYNARR_N(prefs_t,prefs_da,N) wBool_t prefInitted = FALSE; /** * Define the name of the configuration file. Needed size is calculated, allocated and * initialized with the filename * * \param name overwrite default configuration */ void static wlibSetProfileFilename(char *name) { const char *workDir; workDir = wGetAppWorkDir(); if (name && name[0]) { size_t length; length = snprintf(profileFile, 0, "%s", name); profileFile = malloc(length + sizeof(NULL)); snprintf( profileFile, length, "%s", name ); } else { size_t length; length = snprintf(profileFile, 0, "%s/%s.rc", workDir, wConfigName ); profileFile = malloc(length + sizeof(NULL)); length = snprintf(profileFile, length+sizeof(NULL), "%s/%s.rc", workDir, wConfigName ); } } /** * Get the name of the configuration file. * * \return pointer to the filename. * */ char * wGetProfileFilename() { return(profileFile); } /** * Read the configuration file into memory */ static void readPrefs( char * name, wBool_t update ) { char tmp[BUFSIZ+32], *np, *vp, *cp; FILE * prefFile; prefs_t * p; prefInitted = TRUE; wlibSetProfileFilename(name); prefFile = fopen( profileFile, "r" ); if (prefFile == NULL) { // First run, no .rc file yet return; } while ( ( fgets(tmp, sizeof tmp, prefFile) ) != NULL ) { char *sp; sp = tmp; while ( *sp==' ' || *sp=='\t' ) { sp++; } if ( *sp == '\n' || *sp == '#' ) { continue; } np = strchr( sp, '.' ); if (np == NULL) { wNoticeEx( NT_INFORMATION, tmp, _("Continue"), NULL ); continue; } *np++ = '\0'; while ( *np==' ' || *np=='\t' ) { np++; } vp = strchr( np, ':' ); if (vp == NULL) { wNoticeEx( NT_INFORMATION, tmp, _("Continue"), NULL ); continue; } *vp++ = '\0'; while ( *vp==' ' || *vp=='\t' ) { vp++; } cp = vp + strlen(vp) -1; while ( cp >= vp && (*cp=='\n' || *cp==' ' || *cp=='\t') ) { cp--; } cp[1] = '\0'; if (update) { for (int i=0; iname,np)==0 && strcmp(p->section,sp)==0) { p->val = strdup(vp); p->dirty = TRUE; break; } } } else { DYNARR_APPEND( prefs_t, prefs_da, 10 ); p = &prefs(prefs_da.cnt-1); p->name = strdup(np); p->section = strdup(sp); p->dirty = FALSE; p->val = strdup(vp); } } fclose( prefFile ); } /** * Store a string in the user preferences. * * \param section IN section in preferences file * \param name IN name of parameter * \param sval IN value to save */ void wPrefSetString( const char * section, /* Section */ const char * name, /* Name */ const char * sval ) /* Value */ { prefs_t * p; if (!prefInitted) { readPrefs("", FALSE); } for (p=&prefs(0); p<&prefs(prefs_da.cnt); p++) { if ( strcmp( p->section, section ) == 0 && strcmp( p->name, name ) == 0 ) { if (p->val) { free(p->val); } p->dirty = TRUE; p->val = (sval?strdup( sval ):NULL); return; } } DYNARR_APPEND( prefs_t, prefs_da, 10 ); p = &prefs(prefs_da.cnt-1); p->name = strdup(name); p->section = strdup(section); p->dirty = TRUE; p->val = (sval?strdup(sval):NULL); } /** * Get a string from the user preferences. * * \param section IN section in preferences file * \param name IN name of parameter */ char * wPrefGetStringBasic( const char * section, /* Section */ const char * name ) /* Name */ { prefs_t * p; if (!prefInitted) { readPrefs("", FALSE); } for (p=&prefs(0); p<&prefs(prefs_da.cnt); p++) { if ( strcmp( p->section, section ) == 0 && strcmp( p->name, name ) == 0 ) { return p->val; } } return NULL; } /** * Store an integer value in the user preferences. * * \param section IN section in preferences file * \param name IN name of parameter * \param lval IN value to save */ void wPrefSetInteger( const char * section, /* Section */ const char * name, /* Name */ long lval ) /* Value */ { char tmp[20]; snprintf(tmp, sizeof(tmp), "%ld", lval ); wPrefSetString( section, name, tmp ); } /** * Read an integer value from the user preferences. * * \param section IN section in preferences file * \param name IN name of parameter * \param res OUT resulting value * \param default IN default value * \return TRUE if value differs from default, FALSE if the same */ wBool_t wPrefGetIntegerBasic( const char * section, /* Section */ const char * name, /* Name */ long * res, /* Address of result */ long def ) /* Default value */ { const char * cp; char *cp1; cp = wPrefGetStringBasic( section, name ); if (cp == NULL) { *res = def; return FALSE; } *res = strtol(cp,&cp1,0); if (cp==cp1) { *res = def; return FALSE; } return TRUE; } /** * Save a float value in the preferences file. * * \param section IN the file section into which the value should be saved * \param name IN the name of the preference * \param lval IN the value */ void wPrefSetFloat( const char * section, /* Section */ const char * name, /* Name */ double lval ) /* Value */ { char tmp[20]; snprintf(tmp, sizeof(tmp), "%0.6f", lval ); wPrefSetString( section, name, tmp ); } /** * Read a float from the preferencesd file. * * \param section IN the file section from which the value should be read * \param name IN the name of the preference * \param res OUT pointer for the value * \param def IN default value * \return TRUE if value was read, FALSE if default value is used */ wBool_t wPrefGetFloatBasic( const char * section, /* Section */ const char * name, /* Name */ double * res, /* Address of result */ double def ) /* Default value */ { const char * cp; char *cp1; cp = wPrefGetStringBasic( section, name ); if (cp == NULL) { *res = def; return FALSE; } *res = strtod(cp, &cp1); if (cp == cp1) { *res = def; return FALSE; } return TRUE; } void wPrefsLoad(char * name) { readPrefs(name,TRUE); } /** * Save the configuration to a file. The config parameters are held and updated in an array. * To make the settings persistant, this function has to be called. * */ void wPrefFlush( char * name ) { prefs_t * p; char tmp[BUFSIZ+32]; FILE * prefFile; if (!prefInitted) { return; } wlibSetProfileFilename(name); prefFile = fopen( profileFile, "w" ); if (prefFile == NULL) { // Can not write pref file size_t n = BUFSIZ+32-1-strlen(tmp); strncat( tmp, ": ", n ); strncat( tmp, strerror(errno), n-2 ); wNoticeEx( NT_ERROR, tmp, "Ok", NULL ); return; } for (p=&prefs(0); p<&prefs(prefs_da.cnt); p++) { if(p->val) { fprintf( prefFile, "%s.%s: %s\n", p->section, p->name, p->val ); } } fclose( prefFile ); } /** * Clear the preferences from memory * \return */ void wPrefReset( void ) { prefs_t * p; prefInitted = FALSE; for (p=&prefs(0); p<&prefs(prefs_da.cnt); p++) { if (p->section) { free( p->section ); } if (p->name) { free( p->name ); } if (p->val) { free( p->val ); } } prefs_da.cnt = 0; } /** * Split a line from the config file ie. rc ini-file into separate tokens. The * line is split into sections, name of value and value following. Pointers * to the respective token are returned. These are zero-terminated. * If a token is not present, NULL is returned instead. * The input line is modified. * * \param line input line, modified during excution of function * \param section section if present * \param name name of config value if present * \param value name of value if present */ void wPrefTokenize(char* line, char** section, char** name, char** value) { *section = NULL; *name = NULL; *value = NULL; *section = strtok(line, "."); *name = strtok(NULL, ":"); *value = strtok(NULL, "\n"); if(*value) g_strstrip(*value); } /** * A valid line for a config file is created from the individual elements. * Values not need for specific statement are ignored. Eg. when section is * present, name and value are not used. * The caller has to make sure, that the return buffer is large enough. * * \param section section, first token, dellimited with '.' * \param name name, left side of ':' * \param value value, right side of ':' * \param result pointer to buffer for formated line. */ void wPrefFormatLine(const char* section, const char* name, const char* value, char* result) { if (!value || *value == '\0') { value = ""; } sprintf(result, "%s.%s: %s", section, name, value); }