/** \file fileio.c * Loading and saving files. Handles trackplans, XTrackCAD exports and cut/paste */ /* 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 "archive.h" #include "common.h" #include "compound.h" #include "cselect.h" #include "cundo.h" #include "custom.h" #include "directory.h" #include "draw.h" #include "fileio.h" #include "fcntl.h" #include "layout.h" #include "manifest.h" #include "misc.h" #include "param.h" #include "include/paramfile.h" #include "include/paramfilelist.h" #include "paths.h" #include "track.h" #include "version.h" #include "dynstring.h" #include "common-ui.h" #include "ctrain.h" #ifdef UTFCONVERT #include "include/utf8convert.h" #endif // UTFCONVERT EXPORT dynArr_t paramProc_da; #define COPYBLOCKSIZE 1024 EXPORT const char * workingDir; EXPORT const char * libDir; EXPORT char * clipBoardN; static coOrd paste_offset, cursor_offset; EXPORT wBool_t bExample = FALSE; EXPORT wBool_t bReadOnly = FALSE; EXPORT wBool_t bInReadTracks = FALSE; #ifdef WINDOWS #define rename( F1, F2 ) Copyfile( F1, F2 ) #endif EXPORT int Copyfile( const char * fn1, const char * fn2 ) { FILE *f1, *f2; size_t size; f1 = fopen( fn1, "r" ); if ( f1 == NULL ) { return 0; } f2 = fopen( fn2, "w" ); if ( f2 == NULL ) { fclose( f1 ); return -1; } while ( (size=fread( message, 1, sizeof message, f1 )) > 0 ) { fwrite( message, size, 1, f2 ); } fclose( f1 ); fclose( f2 ); return 0; } // // Locale handling // SetCLocale is called before reading/writing any data files (.xtc, .xti, .xtq, .cus...) // SetUserLocale is called after // Calls can be nested: C, C, User, User // static char * sUserLocale = NULL; // current user locale static long lCLocale = 0; // locale state: > 0 C locale, <= 0 user locale static long nCLocale = 0; // total # of setlocals calls static int log_locale = 0; // logging static int log_timereadfile = 0; EXPORT void SetCLocale() { if ( sUserLocale == NULL ) { sUserLocale = MyStrdup( setlocale( LC_ALL, NULL ) ); } if ( lCLocale == 0 ) { LOG( log_locale, 1, ( "Set C Locale: %ld\n", ++nCLocale ) ); setlocale( LC_ALL, "C" ); #ifdef LC_MESSAGES setlocale( LC_MESSAGES, ""); #endif } lCLocale++; if ( lCLocale > 1 ) { LOG( log_locale, 3, ( "SetClocale - C! %ld\n", nCLocale) ); } else if ( lCLocale < 1 ) { LOG( log_locale, 2, ( "SetClocale - User! %ld\n", nCLocale) ); } } EXPORT void SetUserLocale() { if ( lCLocale == 1 ) { LOG( log_locale, 1, ( "Set %s Locale: %ld\n", sUserLocale, ++nCLocale ) ); setlocale( LC_ALL, sUserLocale ); } lCLocale--; if ( lCLocale < 0 ) { LOG( log_locale, 2, ("SetUserLocale - User! %ld\n", nCLocale) ); } else if ( lCLocale > 0 ) { LOG( log_locale, 3, ("SetUserLocale - C! %ld\n", nCLocale) ); } } /**************************************************************************** * * PARAM FILE INPUT * */ EXPORT FILE * paramFile = NULL; char *paramFileName; EXPORT wIndex_t paramLineNum = 0; EXPORT char paramLine[STR_HUGE_SIZE]; EXPORT char * curContents; EXPORT char * curSubContents; #define PARAM_DEMO (-1) dynArr_t paramProc_da; EXPORT void Stripcr( char * line ) { char * cp; cp = line + strlen(line); if (cp == line) { return; } cp--; if (*cp == '\n') { *cp-- = '\0'; } if (cp >= line && *cp == '\r') { *cp = '\0'; } } EXPORT char * GetNextLine( void ) { if (!paramFile) { paramLine[0] = '\0'; return NULL; } if (fgets( paramLine, sizeof paramLine, paramFile ) == NULL) { sprintf( message, "INPUT ERROR: premature EOF on %s", paramFileName ); wNoticeEx( NT_ERROR, message, _("Ok"), NULL ); if ( paramFile ) { fclose( paramFile ); paramFile = NULL; } } Stripcr( paramLine ); ParamCheckSumLine( paramLine ); paramLineNum++; return paramLine; } /** * Show an error message if problems occur during loading of a param or layout file. * The user has the choice to cancel the operation or to continue. If operation is * canceled the open file is closed. * * \param IN msg error message * \param IN showLine set to true if current line should be included in error message * \param IN ... variable number additional error information * \return TRUE to continue, FALSE to abort operation * */ EXPORT int InputError( char * msg, BOOL_T showLine, ... ) { va_list ap; char * mp = message; int ret; mp += sprintf( message, "INPUT ERROR: %s:%d\n", paramFileName, paramLineNum ); va_start( ap, showLine ); mp += vsprintf( mp, msg, ap ); va_end( ap ); if (showLine) { *mp++ = '\n'; strcpy( mp, paramLine ); } strcat( mp, _("\nDo you want to continue?") ); if (!(ret = wNoticeEx( NT_ERROR, message, _("Continue"), _("Stop") ))) { if ( paramFile ) { fclose(paramFile); paramFile = NULL; } if ( paramFileName ) { free( paramFileName ); paramFileName = NULL; } } return ret; } EXPORT void SyntaxError( char * event, wIndex_t actual, wIndex_t expected ) { InputError( "%s scan returned %d (expected %d)", TRUE, event, actual, expected ); } /** * Parse a line in XTrackCAD's file format * * \param line IN line to parse * \param format IN ??? * * \return FALSE in case of parsing error, TRUE on success * In the error case, InputError had been called which may have closed the input file (paramFile) * * format chars are: * 0 - read a number and discard * X - no read, *pi = 0 * Y - no read, *pf = 0L * Z - no read, *pl = 0.0 * L - *pi = number * d - *pi = number * w - *pf = read a width * u - *pul = number * l - *pl = number * f - *pf = number * z - *pf = 0.0 * p - *pp = ( number, number ) a coOrd * s - *ps = string * q - *ps = quoted string * c - *qp = position of next non-space char or NULL */ EXPORT BOOL_T GetArgs( char * line, char * format, ... ) { char * cp, * cq; long * pl; unsigned long *pul; int * pi; FLOAT_T *pf; coOrd p, *pp; char * ps; char ** qp; va_list ap; char * sError = NULL; if ( lCLocale < 1 ) { LOG( log_locale, 1, ( "GetArgs: not in C locale\n" ) ); } cp = line; va_start( ap, format ); for ( ; sError==NULL && *format; format++ ) { while (isspace((unsigned char)*cp)) { cp++; } if (!*cp && strchr( "XZYzc", *format ) == NULL ) { sError = "EOL unexpected"; break; } switch (*format) { case '0': (void)strtol( cp, &cq, 10 ); if (cp == cq) { sError = "%s: expected integer"; break; } cp = cq; break; case 'X': pi = va_arg( ap, int * ); *pi = 0; break; case 'Z': pl = va_arg( ap, long * ); *pl = 0; break; case 'Y': pf = va_arg( ap, FLOAT_T * ); *pf = 0; break; case 'L': pi = va_arg( ap, int * ); *pi = (int)strtol( cp, &cq, 10 ); if (cp == cq) { sError = "%s: expected integer"; break; } cp = cq; break; case 'd': pi = va_arg( ap, int * ); *pi = (int)strtol( cp, &cq, 10 ); if (cp == cq) { sError = "%s: expected integer"; break; } cp = cq; break; case 'w': pf = va_arg( ap, FLOAT_T * ); *pf = (FLOAT_T)strtol( cp, &cq, 10 ); if (cp == cq) { sError = "%s: expected integer"; break; } if (*cq == '.') { *pf = strtod( cp, &cq ); } else { *pf /= mainD.dpi; } cp = cq; break; case 'u': pul = va_arg( ap, unsigned long * ); *pul = strtoul( cp, &cq, 10 ); if (cp == cq) { sError = "%s: expected integer"; break; } cp = cq; break; case 'l': pl = va_arg( ap, long * ); *pl = strtol( cp, &cq, 10 ); if (cp == cq) { sError = "%s: expected integer"; break; } cp = cq; break; case 'f': pf = va_arg( ap, FLOAT_T * ); *pf = strtod( cp, &cq ); if (cp == cq) { sError = "%s: expected float"; break; } cp = cq; break; case 'z': pf = va_arg( ap, FLOAT_T * ); *pf = 0.0; break; case 'p': pp = va_arg( ap, coOrd * ); p.x = strtod( cp, &cq ); if (cp == cq) { sError = "%s: expected float"; break; } cp = cq; p.y = strtod( cp, &cq ); if (cp == cq) { sError = "%s: expected float"; break; } cp = cq; *pp = p; break; case 's': ps = va_arg( ap, char * ); while (isspace((unsigned char)*cp)) { cp++; } while (*cp && !isspace((unsigned char)*cp)) { *ps++ = *cp++; } *ps++ = '\0'; break; case 'q': qp = va_arg( ap, char * * ); if (*cp != '\"') /* Stupid windows */ { cq = strchr( cp, '\"' ); } else { cq = cp; } if (cq!=NULL) { cp = cq; ps = &message[0]; cp++; while (*cp) { CHECK( (ps-message) 2 || inPlayback ) { return; } filename = GetLayoutFilename(); sprintf( message, "%s%s%s - %s(%s)", (filename && filename[0])?filename: _("Unnamed Trackplan"), bReadOnly?_(" (R/O)"):"", changed>0?"*":"", sProdName, sVersion ); wWinSetTitle( mainW, message ); } /***************************************************************************** * * LOAD / SAVE TRACKS * */ static struct wFilSel_t * loadFile_fs = NULL; static struct wFilSel_t * saveFile_fs = NULL; static struct wFilSel_t * examplesFile_fs = NULL; static char * checkPtFileName1; static char * checkPtFileName2; static char * checkPtFileNameBackup; /** Read the layout design. * * \param IN pathName filename including directory * \param IN fileName pointer to filename part in pathName * \param IN full * \param IN noSetCurDir if FALSE current directory is changed to file location * \param IN complain if FALSE error messages are supressed * * \return FALSE in case of load error */ static BOOL_T ReadTrackFile( const char * pathName, const char * fileName, BOOL_T full, BOOL_T noSetCurDir, BOOL_T complain ) { int count; coOrd roomSize; long scale; char * cp; int ret = TRUE; paramFile = fopen( pathName, "r" ); if (paramFile == NULL) { if ( complain ) { NoticeMessage( MSG_OPEN_FAIL, _("Continue"), NULL, sProdName, pathName, strerror(errno) ); } return FALSE; } bInReadTracks = TRUE; SetCLocale(); checkPtFileNameBackup = NULL; paramLineNum = 0; paramFileName = strdup( fileName ); InfoMessage("0"); count = 0; int skipLines = 0; BOOL_T skip = FALSE; while ( paramFile && ( fgets(paramLine, sizeof paramLine, paramFile) ) != NULL ) { count++; BOOL_T old_skip = skip; skip = FALSE; if (count%10 == 0) { InfoMessage( "%d", count ); wFlush(); } paramLineNum++; if (strlen(paramLine) == (sizeof paramLine) -1 && paramLine[(sizeof paramLine)-1] != '\n') { if( !(ret = InputError( "Line too long", TRUE ))) { break; } } Stripcr( paramLine ); if (paramLine[0] == '#' || paramLine[0] == '\n' || paramLine[0] == '\0' ) { /* comment */ continue; } if (ReadTrack( paramLine )) { continue; } else if (IsEND( END_TRK_FILE ) ) { break; } else if (strncmp( paramLine, "VERSION ", 8 ) == 0) { paramVersion = strtol( paramLine+8, &cp, 10 ); if (cp) while (*cp && isspace((unsigned char)*cp)) { cp++; } if ( paramVersion > iParamVersion ) { if (cp && *cp) { NoticeMessage( MSG_UPGRADE_VERSION1, _("Ok"), NULL, paramVersion, iParamVersion, sProdName, cp ); } else { NoticeMessage( MSG_UPGRADE_VERSION2, _("Ok"), NULL, paramVersion, iParamVersion, sProdName ); } break; } if ( paramVersion < iMinParamVersion ) { NoticeMessage( MSG_BAD_FILE_VERSION, _("Ok"), NULL, paramVersion, iMinParamVersion, sProdName ); break; } } else if (!full) { if( !(ret = InputError( "unknown command", TRUE ))) { break; } } else if (strncmp( paramLine, "TITLE1 ", 7 ) == 0) { #ifdef UTFCONVERT ConvertUTF8ToSystem(paramLine + 7); #endif // UTFCONVERT SetLayoutTitle(paramLine + 7); } else if (strncmp( paramLine, "TITLE2 ", 7 ) == 0) { #ifdef UTFCONVERT ConvertUTF8ToSystem(paramLine + 7); #endif // UTFCONVERT SetLayoutSubtitle(paramLine + 7); } else if (strncmp( paramLine, "ROOMSIZE", 8 ) == 0) { if ( ParseRoomSize( paramLine+8, &roomSize ) ) { SetRoomSize( roomSize ); /*wFloatSetValue( roomSizeXPD.control, PutDim(roomSize.x) );*/ /*wFloatSetValue( roomSizeYPD.control, PutDim(roomSize.y) );*/ } else { if( !(ret = InputError( "ROOMSIZE: bad value", TRUE ))) { break; } } } else if (strncmp( paramLine, "SCALE ", 6 ) == 0) { if ( !DoSetScale( paramLine+6 ) ) { if( !(ret = InputError( "SCALE: bad value", TRUE ))) { break; } } } else if (strncmp( paramLine, "MAPSCALE ", 9 ) == 0) { scale = atol( paramLine+9 ); if (scale > 1) { mapD.scale = scale; } } else if (strncmp( paramLine, "LAYERS ", 7 ) == 0) { ReadLayers( paramLine+7 ); } else { if (!old_skip) { if (InputError(_("Unknown layout file object - skip until next good object?"), TRUE)) { //OK to carry on /* SKIP until next main line we recognize */ skip = TRUE; skipLines++; continue; } else { break; //Close File } } else { skip = TRUE; } skipLines++; } } bInReadTracks = FALSE; if (paramFile) { fclose(paramFile); paramFile = NULL; } if( ret ) { if (!noSetCurDir) { SetCurrentPath( LAYOUTPATHKEY, fileName ); } } if (skipLines>0) { NoticeMessage( MSG_LAYOUT_LINES_SKIPPED, _("Ok"), NULL, paramFileName, skipLines); } paramFile = NULL; SetUserLocale(); free(paramFileName); paramFileName = NULL; InfoMessage( "%d", count ); return ret; } int LoadTracks( int cnt, char **fileName, void * data) { char *nameOfFile = NULL; char *extOfFile; CHECK( fileName != NULL ); CHECK( cnt == 1 ); nameOfFile = FindFilename(fileName[0]); // Make sure it exists and it is readable if (access(fileName[0], R_OK) != 0) { NoticeMessage(MSG_OPEN_FAIL, _("Continue"), NULL, _("Track"), nameOfFile, _("Not Found")); return FALSE; } if ( ! bExample ) { SetCurrentPath(LAYOUTPATHKEY, fileName[0]); } bReadOnly = bExample; paramVersion = -1; wSetCursor( mainD.d, wCursorWait ); Reset(); ClearTracks(); ResetLayers(); checkPtMark = changed = 0; if (!data) { LayoutBackGroundInit( TRUE); //Keep values of background -> will be overriden by archive } UndoSuspend(); useCurrentLayer = FALSE; /* * Support zipped filetype */ extOfFile = FindFileExtension( nameOfFile); // BOOL_T zipped = FALSE; BOOL_T loadXTC = TRUE; char * full_path = strdup(fileName[0]); if (extOfFile && (strcmp(extOfFile, ZIPFILETYPEEXTENSION )==0)) { char * zip_input = GetZipDirectoryName(ARCHIVE_READ); //If zipped unpack file into temporary input dir (cleared and re-created) DeleteDirectory(zip_input); SafeCreateDir(zip_input); if (UnpackArchiveFor(fileName[0], nameOfFile, zip_input, FALSE)) { char * manifest_file; MakeFullpath(&manifest_file, zip_input, "manifest.json", NULL); char * manifest = 0; long length; FILE * f = fopen (manifest_file, "rb"); if (f) { fseek(f, 0, SEEK_END); length = ftell(f); fseek(f, 0, SEEK_SET); manifest = malloc(length + 1); if (manifest) { fread(manifest, 1, length, f); manifest[length] = '\0'; } fclose(f); } else { NoticeMessage(MSG_MANIFEST_OPEN_FAIL, _("Continue"), NULL, manifest_file); } free(manifest_file); char * arch_file = NULL; //Set filename to point to included .xtc file //Use the name inside manifest (this helps if a user renames the zip) if (manifest) { arch_file = ParseManifest(manifest, zip_input); free(manifest); } free(full_path); full_path = NULL; // If no manifest value use same name as the archive if (arch_file && arch_file[0]) { MakeFullpath(&full_path, zip_input, arch_file, NULL); } else { MakeFullpath(&full_path, zip_input, nameOfFile, NULL); } nameOfFile = FindFilename(full_path); extOfFile = FindFileExtension(full_path); if (strcmp(extOfFile, ZIPFILETYPEEXTENSION )==0) { for (int i=0; i<4; i++) { extOfFile[i] = extOfFile[i+1]; } } LOG(log_zip, 1, ("Zip-File %s \n", full_path)) #if DEBUG printf("File Path: %s \n", full_path); #endif } else { loadXTC = FALSE; // when unzipping fails, don't attempt loading the trackplan } // zipped = TRUE; free(zip_input); } if ( bExample ) { bReadOnly = TRUE; } else if ( access( fileName[0], W_OK ) == -1 ) { bReadOnly = TRUE; } else { bReadOnly = FALSE; } char *copyOfFileName = MyStrdup(fileName[0]); unsigned long time0 = wGetTimer(); if (loadXTC && ReadTrackFile( full_path, FindFilename( fileName[0]), TRUE, TRUE, TRUE )) { nameOfFile = NULL; extOfFile = NULL; SetCurrentPath( LAYOUTPATHKEY, copyOfFileName ); SetLayoutFullPath(copyOfFileName); SetWindowTitle(); if ( ! bExample && (nameOfFile != NULL) ) { char * copyFile = strdup(fileName[0]); char * listName = FindFilename(strdup( fileName[0])); //Make sure the list name is new wMenuListAdd( fileList_ml, 0, listName, copyFile ); } ResolveIndex(); LOG( log_timereadfile, 1, ( "Read time (%s) = %lu mS \n", fileName[0], wGetTimer()-time0 ) ); RecomputeElevations(NULL); AttachTrains(); DoChangeNotification( CHANGE_ALL ); DoUpdateTitles(); LayerSetCounts(); } MyFree(copyOfFileName); free(full_path); full_path = NULL; UpdateLayerDlg(curLayer); UndoResume(); Reset(); wSetCursor( mainD.d, defaultCursor ); return TRUE; } /** * Load the layout specified by data. Filename may contain a full * path. * \param index IN ignored * \param label IN if not NULL - during startup - set flag to not load background * \param data IN path and filename */ EXPORT void DoFileList( int index, char * label, void * data ) { char *pathName = (char*)data; bExample = FALSE; if (label) { LoadTracks( 1, &pathName, I2VP(1)); } else { LoadTracks( 1, &pathName, NULL ); } } static BOOL_T DoSaveTracks( const char * fileName ) { FILE * f; time_t clock; BOOL_T rc = TRUE; f = fopen( fileName, "w" ); if (f==NULL) { NoticeMessage( MSG_OPEN_FAIL, _("Continue"), NULL, _("Track"), fileName, strerror(errno) ); return FALSE; } SetCLocale(); wSetCursor( mainD.d, wCursorWait ); time(&clock); rc &= fprintf(f,"#%s Version: %s, Date: %s\n", sProdName, sVersion, ctime(&clock) )>0; rc &= fprintf(f, "VERSION %d %s\n", iParamVersion, PARAMVERSIONVERSION )>0; Stripcr( GetLayoutTitle() ); Stripcr( GetLayoutSubtitle() ); rc &= fprintf(f, "TITLE1 %s\n", GetLayoutTitle())>0; rc &= fprintf(f, "TITLE2 %s\n", GetLayoutSubtitle())>0; rc &= fprintf(f, "MAPSCALE %ld\n", (long)mapD.scale )>0; rc &= fprintf(f, "ROOMSIZE %0.6f x %0.6f\n", mapD.size.x, mapD.size.y )>0; rc &= fprintf(f, "SCALE %s\n", curScaleName )>0; rc &= WriteLayers( f ); rc &= WriteMainNote( f ); rc &= WriteTracks( f, TRUE ); rc &= fprintf(f, "%s\n", END_TRK_FILE)>0; if ( !rc ) { NoticeMessage( MSG_WRITE_FAILURE, _("Ok"), NULL, strerror(errno), fileName ); } fclose(f); bReadOnly = FALSE; checkPtMark = changed; wSetCursor( mainD.d, defaultCursor ); SetUserLocale(); return rc; } /************************************************ * Copy Dependency - copy file into another directory * * \param IN name * \param IN target_dir * * \returns TRUE for success * */ static BOOL_T CopyDependency(char * name, char * target_dir) { char * backname = FindFilename(name); BOOL_T copied = TRUE; FILE * source = fopen(name, "rb"); if (source != NULL) { char * target_file; MakeFullpath(&target_file, target_dir, backname, NULL); FILE * target = fopen(target_file, "wb"); if (target != NULL) { char *buffer = MyMalloc(COPYBLOCKSIZE); while (!feof(source)) { size_t bytes = fread(buffer, 1, sizeof(buffer), source); if (bytes) { fwrite(buffer, 1, bytes, target); } } MyFree(buffer); LOG(log_zip, 1, ("Zip-Include %s into %s \n", name, target_file)) #if DEBUG printf("xtrkcad: Included file %s into %s \n", name, target_file); #endif fclose(target); } else { NoticeMessage(MSG_COPY_FAIL, _("Continue"), NULL, name, target_file); copied = FALSE; } free(target_file); fclose(source); } else { NoticeMessage(MSG_COPY_OPEN_FAIL, _("Continue"), NULL, name); copied = FALSE; } return copied; } static doSaveCallBack_p doAfterSave; /** * Save the layout to file. This function handles either cases, classic xtc * files as well as xtce zip archives. * * \param cnt Number of files, must be 1 * \param fileName name of destination file including extension (xtc or xtce) * \param data unused * * \returns TRUE for success */ static int SaveTracks( int cnt, char** fileName, void* data) { BOOL_T success = FALSE; CHECK(fileName != NULL); CHECK(cnt == 1); char* nameOfFile = FindFilename(fileName[0]); SetCurrentPath(LAYOUTPATHKEY, fileName[0]); //Support Archive zipped files char* extOfFile = FindFileExtension(fileName[0]); if (extOfFile && (strcmp(extOfFile, ZIPFILETYPEEXTENSION) == 0)) { char* ArchiveName; //Set filename to point to be the same as the included .xtc file. //This is also in the manifest - in case a user renames the archive file. char* zip_output = GetZipDirectoryName(ARCHIVE_WRITE); DeleteDirectory(zip_output); SafeCreateDir(zip_output); MakeFullpath(&ArchiveName, zip_output, nameOfFile, NULL); nameOfFile = FindFilename(ArchiveName); extOfFile = FindFileExtension(ArchiveName); if (extOfFile && strcmp(extOfFile, ZIPFILETYPEEXTENSION) == 0) { // Get rid of the 'e' extOfFile[3] = '\0'; } char* DependencyDir; // The included files are placed (for now) into an includes directory - // TODO an array of includes with directories by type MakeFullpath(&DependencyDir, zip_output, "includes", NULL); SafeCreateDir(DependencyDir); char* background = GetLayoutBackGroundFullPath(); // if used, get the background file // else ignore this step if (background && background[0]) { success = CopyDependency(background, DependencyDir); } else { background = NULL; success = TRUE; } if (success) { //The details are stored into the manifest - TODO use arrays for files, locations SetCLocale(); char* json_Manifest = CreateManifest(nameOfFile, background, "includes"); char* manifest_file; MakeFullpath(&manifest_file, zip_output, "manifest.json", NULL); FILE* fp = fopen(manifest_file, "wb"); if (fp != NULL) { fputs(json_Manifest, fp); fclose(fp); } else { NoticeMessage(MSG_MANIFEST_FAIL, _("Continue"), NULL, manifest_file); success = FALSE; } SetUserLocale(); free(manifest_file); free(json_Manifest); } success &= DoSaveTracks(ArchiveName); if (success) { if (CreateArchive(zip_output, fileName[0]) != TRUE) { NoticeMessage(MSG_ARCHIVE_FAIL, _("Continue"), NULL, fileName[0], zip_output); } } free(zip_output); free(ArchiveName); } else { success = DoSaveTracks(fileName[0]); } if (success) { nameOfFile = FindFilename(fileName[0]); wMenuListAdd(fileList_ml, 0, nameOfFile, MyStrdup(fileName[0])); checkPtMark = changed = 0; SetLayoutFullPath(fileName[0]); } if (doAfterSave) { doAfterSave(); } return success; } /** * Save information about current files and some settings to preferences file. */ EXPORT void SaveState(void) { wWinPix_t width, height; const char * fileName; void * pathName; char file[6]; int inx; wWinGetSize(mainW, &width, &height); wPrefSetInteger("draw", "mainwidth", (int)width); wPrefSetInteger("draw", "mainheight", (int)height); SaveParamFileList(); ParamUpdatePrefs(); wPrefSetString( "misc", "lastlayout", GetLayoutFullPath()); wPrefSetInteger( "misc", "lastlayoutexample", bExample ); if (fileList_ml) { strcpy(file, "file"); file[5] = 0; for (inx = 0; inx < NUM_FILELIST; inx++) { fileName = wMenuListGet(fileList_ml, inx, &pathName); if (fileName) { file[4] = '0' + inx; sprintf(message, "%s", (char* )pathName); wPrefSetString("filelist", file, message); } } } wPrefFlush(""); } static void SetAutoSave() { if (saveFile_fs == NULL) saveFile_fs = wFilSelCreate( mainW, FS_SAVE, 0, _("AutoSave Tracks As"), sSourceFilePattern, SaveTracks, NULL ); wFilSelect( saveFile_fs, GetCurrentPath(LAYOUTPATHKEY)); checkPtMark = 1; SetWindowTitle(); CleanupCheckpointFiles(); //Remove old checkpoint SaveState(); } EXPORT void DoSave( void * doAfterSaveVP ) { doAfterSave = doAfterSaveVP; if ( bReadOnly || *(GetLayoutFilename()) == '\0') { if (saveFile_fs == NULL) saveFile_fs = wFilSelCreate( mainW, FS_SAVE, 0, _("Save Tracks"), sSourceFilePattern, SaveTracks, NULL ); wFilSelect( saveFile_fs, GetCurrentPath(LAYOUTPATHKEY)); checkPtMark = 1; } else { char *temp = GetLayoutFullPath(); SaveTracks( 1, &temp, NULL ); } SetWindowTitle(); CleanupCheckpointFiles(); //Remove old checkpoint SaveState(); } EXPORT void DoSaveAs( void * doAfterSaveVP ) { doAfterSave = doAfterSaveVP; if (saveFile_fs == NULL) saveFile_fs = wFilSelCreate( mainW, FS_SAVE, 0, _("Save Tracks As"), sSaveFilePattern, SaveTracks, NULL ); wFilSelect( saveFile_fs, GetCurrentPath(LAYOUTPATHKEY)); checkPtMark = 1; SetWindowTitle(); CleanupCheckpointFiles(); //Remove old checkpoint SaveState(); } EXPORT void DoLoad( void ) { if (loadFile_fs == NULL) loadFile_fs = wFilSelCreate( mainW, FS_LOAD, 0, _("Open Tracks"), sSourceFilePattern, LoadTracks, NULL ); bExample = FALSE; wFilSelect( loadFile_fs, GetCurrentPath(LAYOUTPATHKEY)); paste_offset = zero; cursor_offset = zero; CleanupCheckpointFiles(); //Remove old checkpoint SaveState(); } EXPORT void DoExamples( void ) { if (examplesFile_fs == NULL) { examplesFile_fs = wFilSelCreate( mainW, FS_LOAD, 0, _("Example Tracks"), sSourceFilePattern, LoadTracks, NULL ); } bExample = TRUE; sprintf( message, "%s" FILE_SEP_CHAR "examples" FILE_SEP_CHAR, libDir ); wFilSelect( examplesFile_fs, message ); CleanupCheckpointFiles(); //Remove old checkpoint SaveState(); } static wIndex_t generations_count = 0; wIndex_t max_generations_count = 10; static char sCheckPointBF[STR_LONG_SIZE]; static void DoCheckPoint( void ) { int rc; if (!checkPtFileNameBackup || (changed <= checkPtInterval+1)) { sprintf(sCheckPointBF,"%s00.bkp",GetLayoutFilename()); MakeFullpath(&checkPtFileNameBackup, workingDir, sCheckPointBF, NULL); } rename( checkPtFileName1, checkPtFileName2 ); rc = DoSaveTracks( checkPtFileName1 ); /* could the check point file be written ok? */ if( rc ) { /* yes, archive/delete the backup copy of the checkpoint file */ if (checkPtFileNameBackup) { char * spot = strrchr(checkPtFileNameBackup,'.'); if (spot && spot>checkPtFileNameBackup+3) { spot[-2]=generations_count/10+'0'; spot[-1]=generations_count%10+'0'; } generations_count++; if (((autosaveChkPoints == 0) && (generations_count > 5)) || ((autosaveChkPoints > 0) && (generations_count > autosaveChkPoints)) ) { generations_count = 0; } remove( checkPtFileNameBackup); rename( checkPtFileName2, checkPtFileNameBackup ); } else { remove(checkPtFileName2); } } else { /* no, rename the backup copy back to the checkpoint file name */ rename( checkPtFileName2, checkPtFileName1 ); } wShow( mainW ); } static wIndex_t autosave_count = 0; EXPORT wIndex_t checkPtMark = 0; EXPORT long checkPtInterval = 10; EXPORT long autosaveChkPoints = 0; EXPORT void TryCheckPoint() { if (checkPtInterval > 0 && changed >= checkPtMark + (wIndex_t) checkPtInterval && !inPlayback) { DoCheckPoint(); checkPtMark = changed; autosave_count++; if ((autosaveChkPoints>0) && (autosave_count>=autosaveChkPoints)) { if ( bReadOnly || *(GetLayoutFilename()) == '\0') { SetAutoSave(); } else { DoSave(NULL); } InfoMessage(_("File AutoSaved")); autosave_count = 0; } } } /** * Remove all temporary files before exiting. When the program terminates * normally through the exit choice, files and directories that were created * temporarily are removed: xtrkcad.ckp * * \param none * \return none * */ EXPORT void CleanupCheckpointFiles( void ) { if( checkPtFileName1 ) { if (checkPtFileNameBackup) { remove( checkPtFileNameBackup ); rename( checkPtFileName1, checkPtFileNameBackup ); } remove( checkPtFileName1 ); } } /** * Remove all temporary files used for archive handling. When the program terminates * normally through the exit choice, files and directories that were created * temporarily are removed: zip_in. and zip_out. * * \param none * \return none * */ EXPORT void CleanupTempArchive(void) { char* tempDir; for (int i = ARCHIVE_READ; i <= ARCHIVE_WRITE; ++i) { tempDir = GetZipDirectoryName(i); if (tempDir) { DeleteDirectory(tempDir); free(tempDir); } } } /** * Check for existence of checkpoint file. Existence of a checkpoint file means that XTrkCAD was not properly * terminated. * * \param none * \return TRUE if exists, FALSE otherwise * */ EXPORT int ExistsCheckpoint( void ) { struct stat fileStat; MakeFullpath(&checkPtFileName1, workingDir, sCheckPointF, NULL); MakeFullpath(&checkPtFileName2, workingDir, sCheckPoint1F, NULL); if( !stat( checkPtFileName1, &fileStat ) ) { return TRUE; } else { return FALSE; } } /** * Load checkpoint file * * \param if TRUE reuse old filename * \param filename returned * \return TRUE if exists, FALSE otherwise * */ EXPORT int LoadCheckpoint( BOOL_T sameName ) { char *search; paramVersion = -1; wSetCursor( mainD.d, wCursorWait ); MakeFullpath(&search, workingDir, sCheckPointF, NULL); UndoSuspend(); if (ReadTrackFile( search, search + strlen(search) - strlen( sCheckPointF ), TRUE, TRUE, TRUE )) { ResolveIndex(); LayoutBackGroundInit(FALSE); //Get Prior BackGround LayoutBackGroundSave(); //Save Background Values if (sameName) { long iExample; char * initialFile = (char*)wPrefGetString("misc", "lastlayout"); wPrefGetInteger("misc", "lastlayoutexample", &iExample, 0); bExample = (iExample == 1); if (initialFile && strlen(initialFile)) { SetCurrentPath( LAYOUTPATHKEY, initialFile ); SetLayoutFullPath(initialFile); } } else { SetLayoutFullPath(""); } RecomputeElevations(NULL); AttachTrains(); DoChangeNotification( CHANGE_ALL ); DoUpdateTitles(); } else { SetLayoutFullPath(""); } LayerSetCounts(); UpdateLayerDlg(curLayer); Reset(); UndoResume(); wSetCursor( mainD.d, defaultCursor ); SetWindowTitle(); checkPtMark = changed = 1; free( search ); return TRUE; } /***************************************************************************** * * IMPORT / EXPORT * */ static struct wFilSel_t * exportFile_fs; static struct wFilSel_t * importFile_fs; static int importAsModule; /******************************************************************************* * * Import Layout Dialog * */ static int ImportTracks( int cnt, char **fileName, void * data ) { char *nameOfFile; long paramVersionOld = paramVersion; CHECK( fileName != NULL ); CHECK( cnt == 1 ); nameOfFile = FindFilename(fileName[ 0 ]); paramVersion = -1; wSetCursor( mainD.d, wCursorWait ); Reset(); SetAllTrackSelect( FALSE ); int saveLayer = curLayer; int layer = 0; if (importAsModule) { layer = FindUnusedLayer(0); if (layer==-1) { return FALSE; } char LayerName[80]; LayerName[0] = '\0'; sprintf(LayerName,_("Module - %s"),nameOfFile); if (layer>=0) { SetCurrLayer(layer, NULL, 0, NULL, NULL); } SetLayerName(layer,LayerName); } ImportStart(); UndoStart( _("Import Tracks"), "importTracks" ); useCurrentLayer = TRUE; ReadTrackFile( fileName[ 0 ], nameOfFile, FALSE, FALSE, TRUE ); ImportEnd(zero, TRUE, FALSE); if (importAsModule) { SetLayerModule(layer,TRUE); } useCurrentLayer = FALSE; SetCurrLayer(saveLayer, NULL, 0, NULL, NULL); /*DoRedraw();*/ EnableCommands(); wSetCursor( mainD.d, defaultCursor ); paramVersion = paramVersionOld; DoCommandB( I2VP(selectCmdInx) ); SelectRecount(); return TRUE; } EXPORT void DoImport( void * type ) { importAsModule = (int)VP2L(type); if (importFile_fs == NULL) importFile_fs = wFilSelCreate( mainW, FS_LOAD, 0, type == 0 ? _("Import Tracks") : _("Import Module"), sImportFilePattern, ImportTracks, NULL ); wFilSelect( importFile_fs, GetCurrentPath(LAYOUTPATHKEY)); } /** * Export the selected track pieces * * \param cnt IN Count of filenames, should always be 1 * \param fileName IN array of fileNames with cnt names * \param data IN unused * \return FALSE on error, TRUE on success */ static int DoExportTracks( int cnt, char **fileName, void * data ) { FILE * f; time_t clock; CHECK( fileName != NULL ); CHECK( cnt == 1 ); SetCurrentPath( IMPORTPATHKEY, fileName[ 0 ] ); f = fopen( fileName[ 0 ], "w" ); if (f==NULL) { NoticeMessage( MSG_OPEN_FAIL, _("Continue"), NULL, _("Export"), fileName[0], strerror(errno) ); return FALSE; } SetCLocale(); wSetCursor( mainD.d, wCursorWait ); time(&clock); fprintf(f,"#%s Version: %s, Date: %s\n", sProdName, sVersion, ctime(&clock) ); fprintf(f, "VERSION %d %s\n", iParamVersion, PARAMVERSIONVERSION ); coOrd offset; ExportTracks( f, &offset ); fprintf(f, "%s\n", END_TRK_FILE); fclose(f); SetUserLocale(); Reset(); wSetCursor( mainD.d, defaultCursor ); UpdateAllElevations(); return TRUE; } EXPORT void DoExport( void * unused ) { if (exportFile_fs == NULL) exportFile_fs = wFilSelCreate( mainW, FS_SAVE, 0, _("Export Tracks"), sImportFilePattern, DoExportTracks, NULL ); wFilSelect( exportFile_fs, GetCurrentPath(LAYOUTPATHKEY)); } EXPORT wBool_t editStatus = TRUE; EXPORT void EditCopy( void * unused ) { editStatus = FALSE; FILE * f; time_t clock; if (selectedTrackCount <= 0) { ErrorMessage( MSG_NO_SELECTED_TRK ); return; } f = fopen( clipBoardN, "w" ); if (f == NULL) { NoticeMessage( MSG_OPEN_FAIL, _("Continue"), NULL, _("Clipboard"), clipBoardN, strerror(errno) ); return; } SetCLocale(); time(&clock); fprintf(f,"#%s Version: %s, Date: %s\n", sProdName, sVersion, ctime(&clock) ); fprintf(f, "VERSION %d %s\n", iParamVersion, PARAMVERSIONVERSION ); ExportTracks(f, &paste_offset ); fprintf(f, "%s\n", END_TRK_FILE ); SetUserLocale(); fclose(f); editStatus = TRUE; } EXPORT void EditCut( void * unused ) { EditCopy(NULL); if ( !editStatus ) { return; } SelectDelete(); } /** * Paste clipboard content. XTrackCAD uses a disk file as clipboard replacement. This file is read and the * content is inserted. * * \return TRUE if success, FALSE on error (file not found) */ static BOOL_T EditPastePlace( wBool_t inPlace ) { BOOL_T rc = TRUE; wSetCursor( mainD.d, wCursorWait ); Reset(); SetAllTrackSelect( FALSE ); double offset = 20*mainD.scale/mainD.dpi; paste_offset.x += offset; paste_offset.y += offset; ImportStart(); UndoStart( _("Paste"), "paste" ); useCurrentLayer = TRUE; if ( !ReadTrackFile( clipBoardN, sClipboardF, FALSE, TRUE, FALSE ) ) { NoticeMessage( MSG_CANT_PASTE, _("Continue"), NULL ); rc = FALSE; } if (inPlace) { ImportEnd(paste_offset, FALSE, TRUE); } else { ImportEnd(zero, FALSE, FALSE); } useCurrentLayer = FALSE; /*DoRedraw();*/ EnableCommands(); wSetCursor( mainD.d, defaultCursor ); DoCommandB( I2VP(selectCmdInx) ); SelectRecount(); UpdateAllElevations(); return rc; } EXPORT void EditPaste( void * unused ) { editStatus = EditPastePlace(FALSE); } EXPORT void EditClone( void * unused ) { EditCopy( NULL ); if ( !editStatus ) { return; } editStatus = EditPastePlace(TRUE); } /***************************************************************************** * * INITIALIZATION * */ EXPORT void LoadFileList(void) { char file[6]; int inx; const char * cp; const char *fileName, *pathName; strcpy(file, "fileX"); for (inx = NUM_FILELIST - 1; inx >= 0; inx--) { file[4] = '0' + inx; cp = wPrefGetString("filelist", file); if (!cp) { continue; } pathName = MyStrdup(cp); fileName = FindFilename((char *) pathName); if (fileName) { wMenuListAdd(fileList_ml, 0, fileName, pathName); } } } EXPORT void FileInit( void ) { libDir = wGetAppLibDir(); CHECK( libDir ); workingDir = wGetAppWorkDir(); CHECK( workingDir ); SetLayoutFullPath(""); MakeFullpath(&clipBoardN, workingDir, sClipboardF, NULL); log_locale = LogFindIndex( "locale" ); log_timereadfile = LogFindIndex( "timereadfile" ); }