/** \file paramfilelist.c * Handling of the list of parameter files */ /* XTrackCad - Model Railroad CAD * Copyright (C) 2019 Martin Fischer * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "common.h" #include "compound.h" #include "ctrain.h" #include "custom.h" #include "dynstring.h" #include "fileio.h" #include "layout.h" #include "misc2.h" #include "paths.h" #include "include/paramfile.h" #include "include/paramfilelist.h" #include "common-ui.h" dynArr_t paramFileInfo_da; int curParamFileIndex = PARAM_DEMO; static int log_params; static char * customPath; static char * customPathBak; #define FAVORITESECTION "Parameter File Favorites" #define FAVORITETOTALS "Total" #define FAVORITEKEY "Favorite%d" #define FAVORITEDELETED "Deleted%d" int GetParamFileCount() { return (paramFileInfo_da.cnt); } /** * Show parameter file error message * * \param [in,out] file If non-null, the file. */ static void ReadParamError(char *file) { DynString error_msg; DynStringMalloc(&error_msg, 100); DynStringPrintf(&error_msg, _("The parameter file: %s could not be found and was probably deleted or moved. " "The file is removed from the active parameter file list."), file); wNoticeEx(NT_ERROR, DynStringToCStr(&error_msg), "OK", NULL); DynStringFree(&error_msg); } /** * Update the configuration file in case the name of a parameter file has changed. * The function reads a list of new parameter filenames and gets the contents * description for each file. If that contents is in use, i.e. is a loaded parameter * file, the setting in the config file is updated to the new filename. * First line of update file has the date of the update file. * * Following lines have filenames, one per line. * * \return FALSE if update not possible or not necessary, TRUE if update successful */ static BOOL_T UpdateParamFiles(void) { char fileName[STR_LONG_SIZE], *fileNameP; const char * cp; FILE * updateF; long updateTime; long lastTime; MakeFullpath(&fileNameP, libDir, "xtrkcad.upd", NULL); updateF = fopen(fileNameP, "r"); free(fileNameP); if (updateF == NULL) { return FALSE; } if (fgets(message, sizeof message, updateF) == NULL) { NoticeMessage("short file: xtrkcad.upd", _("Ok"), NULL); fclose(updateF); return FALSE; } wPrefGetInteger("file", "updatetime", &lastTime, 0); updateTime = atol(message); if (lastTime >= updateTime) { fclose(updateF); return FALSE; } while ((fgets(fileName, STR_LONG_SIZE, updateF)) != NULL) { FILE * paramF; char * contents; Stripcr(fileName); InfoMessage(_("Updating %s"), fileName); MakeFullpath(&fileNameP, libDir, "params", fileName, NULL); paramF = fopen(fileNameP, "r"); if (paramF == NULL) { NoticeMessage(MSG_PRMFIL_OPEN_NEW, _("Ok"), NULL, fileNameP); free(fileNameP); continue; } contents = NULL; while ((fgets(message, sizeof message, paramF)) != NULL) { if (strncmp(message, "CONTENTS", 8) == 0) { Stripcr(message); contents = message + 9; break; } } fclose(paramF); if (contents == NULL) { NoticeMessage(MSG_PRMFIL_NO_CONTENTS, _("Ok"), NULL, fileNameP); free(fileNameP); continue; } cp = wPrefGetString("Parameter File Map", contents); wPrefSetString("Parameter File Map", contents, fileNameP); if (cp != NULL && *cp != '\0') { /* been there, done that */ free(fileNameP); continue; } free(fileNameP); } fclose(updateF); wPrefSetInteger("file", "updatetime", updateTime); return TRUE; } /** * Read the list of parameter files from configuration and load the files. * */ void LoadParamFileList(void) { int fileNo; BOOL_T updated = FALSE; long *favoriteList = NULL; long favorites; int nextFavorite = 0; updated = UpdateParamFiles(); wPrefGetIntegerBasic(FAVORITESECTION, FAVORITETOTALS, &favorites, 0); if (favorites) { DynString topic; favoriteList = MyMalloc(sizeof(long)*favorites); if (!favoriteList) { AbortProg("Couldn't allocate memory for favorite list!\n"); } DynStringMalloc(&topic, 16); for (int i = 0; i < favorites; i++) { DynStringPrintf(&topic, FAVORITEKEY, i); wPrefGetIntegerBasic(FAVORITESECTION, DynStringToCStr(&topic), &favoriteList[i], 0); } DynStringFree(&topic); } for (fileNo = 1; ; fileNo++) { char *fileName; const char * contents; enum paramFileState structState = PARAMFILE_UNLOADED; sprintf(message, "File%d", fileNo); contents = wPrefGetString("Parameter File Names", message); if (contents == NULL || *contents == '\0') { break; } InfoMessage("Parameters for %s", contents); fileName = wPrefGetString("Parameter File Map", contents); if (fileName == NULL || *fileName == '\0') { NoticeMessage(MSG_PRMFIL_NO_MAP, _("Ok"), NULL, contents); continue; } char * share; // Rewire to the latest system level #define SHAREPARAMS (PATH_SEPARATOR "share" PATH_SEPARATOR "xtrkcad" PATH_SEPARATOR "params" PATH_SEPARATOR) if ((share= strstr(fileName,SHAREPARAMS))) { share += strlen(SHAREPARAMS); MakeFullpath(&fileName, wGetAppLibDir(), "params", share, NULL); wPrefSetString("Parameter File Map", contents, fileName); } if (ReadParamFile(fileName) >= 0) { if (curContents == NULL) { curContents = curSubContents = MyStrdup(contents); } paramFileInfo(curParamFileIndex).contents = curContents; if (favoriteList && fileNo == favoriteList[nextFavorite]) { DynString topic; long deleted; DynStringMalloc(&topic, 16); DynStringPrintf(&topic, FAVORITEDELETED, fileNo); wPrefGetIntegerBasic(FAVORITESECTION, DynStringToCStr(&topic), &deleted, 0L); paramFileInfo(curParamFileIndex).favorite = TRUE; paramFileInfo(curParamFileIndex).deleted = deleted; if (nextFavorite < favorites - 1) { nextFavorite++; } DynStringFree(&topic); } } else { ReadParamError(fileName); } } curParamFileIndex = PARAM_CUSTOM; if (updated) { SaveParamFileList(); } MyFree(favoriteList); } /** * Save the currently selected parameter files. Parameter files that have been unloaded * are not saved. * */ void SaveParamFileList(void) { int fileInx; int fileNo; int favorites; char * contents, *cp; for (fileInx = 0, fileNo = 1, favorites = 0; fileInx < paramFileInfo_da.cnt; fileInx++) { if (paramFileInfo(fileInx).valid && (!paramFileInfo(fileInx).deleted || paramFileInfo(fileInx).favorite)) { sprintf(message, "File%d", fileNo); contents = paramFileInfo(fileInx).contents; for (cp = contents; *cp; cp++) { if (*cp == '=' || *cp == '\'' || *cp == '"' || *cp == ':' || *cp == '.') { *cp = ' '; } } wPrefSetString("Parameter File Names", message, contents); wPrefSetString("Parameter File Map", contents, paramFileInfo(fileInx).name); if (paramFileInfo(fileInx).favorite) { sprintf(message, FAVORITEKEY, favorites); wPrefSetInteger(FAVORITESECTION, message, fileNo); sprintf(message, FAVORITEDELETED, fileNo); wPrefSetInteger(FAVORITESECTION, message, paramFileInfo(fileInx).deleted); favorites++; } fileNo++; } } sprintf(message, "File%d", fileNo); wPrefSetString("Parameter File Names", message, ""); wPrefSetInteger(FAVORITESECTION, FAVORITETOTALS, favorites); } void UpdateParamFileList(void) { for (size_t i = 0; i < (unsigned)paramFileInfo_da.cnt; i++) { SetParamFileState((int)i); } } /** * Load the selected parameter files. This is a callback executed when the file selection dialog * is closed. * Steps: * - the parameters are read from file * - check is performed to see whether the content is already present, if yes the previously * loaded content is invalidated * - loaded parameter file is added to list of parameter files * - if a parameter file dialog exists the list is updated. It is either rewritten in * in case of an invalidated file or the new file is appended * - the settings are updated * These steps are repeated for every file in list * * \param files IN the number of filenames in the fileName array * \param fileName IN an array of fully qualified filenames * \param data IN ignored * \return TRUE on success, FALSE on error */ int LoadParamFile( int files, char ** fileName, void * data) { wIndex_t inx; int i = 0; assert(fileName != NULL); assert(files > 0); for (i = 0; i < files; i++) { enum paramFileState structState = PARAMFILE_UNLOADED; int newIndex; curContents = curSubContents = NULL; newIndex = ReadParamFile(fileName[i]); if (newIndex >= 0) { // in case the contents is already present, make invalid for (inx = 0; inx < newIndex; inx++) { if (paramFileInfo(inx).valid && strcmp(paramFileInfo(inx).contents, curContents) == 0) { paramFileInfo(inx).valid = FALSE; break; } } wPrefSetString("Parameter File Map", curContents, paramFileInfo(curParamFileIndex).name); } else { ReadParamError(fileName[i]); } } //Only set the ParamFileDir if not the system directory if (!strstr(fileName[i-1],SHAREPARAMS)) SetParamFileDir(fileName[i - 1]); curParamFileIndex = PARAM_CUSTOM; DoChangeNotification(CHANGE_PARAMS); return TRUE; } static void ReadCustom(void) { FILE * f; MakeFullpath(&customPath, workingDir, sCustomF, NULL); customPathBak = MyStrdup(customPath); customPathBak[ strlen(customPathBak)-1 ] = '1'; f = fopen(customPath, "r"); if (f != NULL) { fclose(f); curParamFileIndex = PARAM_CUSTOM; ReadParams(0, workingDir, sCustomF); } } /** * Open the custom file where user-defined turnouts, cars and such are stored */ FILE * OpenCustom(char *mode) { FILE * ret = NULL; if (inPlayback) { return NULL; } if (*mode == 'w') { rename(customPath, customPathBak); } if (customPath) { ret = fopen(customPath, mode); if (ret == NULL) { NoticeMessage(MSG_OPEN_FAIL, _("Continue"), NULL, _("Custom"), customPath, strerror(errno)); } } return ret; } /** * Update and open the parameter files dialog box * * \param junk */ static void DoParamFileListDialog(void *junk) { DoParamFiles(junk); ParamFileListLoad(paramFileInfo_da.cnt, ¶mFileInfo_da); } addButtonCallBack_t ParamFilesInit(void) { RegisterChangeNotification(ParamFilesChange); return &DoParamFileListDialog; } /** * Get the initial parameter files. The Xtrkcad.xtq file containing scale and * demo definitions is read. * * \return FALSE on error, TRUE otherwise */ BOOL_T ParamFileListInit(void) { /** @logcmd @showrefby params=n paramfilelist.c Log ReadParams * (including scale file (xtq), custom file (*.cus) and other params (xtp)) */ log_params = LogFindIndex("params"); SetCLocale(); // get the default definitions if (ReadParams(lParamKey, libDir, sParamQF) == FALSE) { SetUserLocale(); return FALSE; } curParamFileIndex = PARAM_CUSTOM; if (lParamKey == 0) { LoadParamFileList(); ReadCustom(); } SetUserLocale(); return TRUE; } /** * Deletes all parameter types described by index * * \param index Zero-based index of the. */ static void DeleteAllParamTypes(int index) { DeleteTurnoutParams(index); DeleteCarProto(index); DeleteCarPart(index); DeleteStructures(index); } /** * Unload parameter file: all parameter definitions from this file are deleted * from memory. Strings allocated to store the filename and contents * description are free'd as well. * In order to keep the overall data structures consistent, the file info * structure is not removed from the array but flagged as garbage * * \param fileIndex Zero-based index of the file. * * \returns True if it succeeds, false if it fails. */ bool UnloadParamFile(wIndex_t fileIndex) { paramFileInfo_p paramFileI = ¶mFileInfo(fileIndex); DeleteAllParamTypes(fileIndex); MyFree(paramFileI->name); MyFree(paramFileI->contents); paramFileI->valid = FALSE; for (int i = 0; i < paramFileInfo_da.cnt; i++) { LOG1(log_params, ("UnloadParamFiles: = %s: %d\n", paramFileInfo(i).contents, paramFileInfo(i).trackState)) } return (true); } /** * Reload parameter file * * \param index Zero-based index of the paramFileInfo struct. * * \returns True if it succeeds, false if it fails. */ bool ReloadParamFile(wIndex_t index) { paramFileInfo_p paramFileI = ¶mFileInfo(index); DeleteAllParamTypes(index); MyFree(paramFileI->contents); ReloadDeletedParamFile(index); return(true); }