diff --git a/ADApp/ADSrc/asynNDArrayDriver.cpp b/ADApp/ADSrc/asynNDArrayDriver.cpp index 8f2d7d2c6..419e1e50e 100644 --- a/ADApp/ADSrc/asynNDArrayDriver.cpp +++ b/ADApp/ADSrc/asynNDArrayDriver.cpp @@ -542,6 +542,7 @@ asynNDArrayDriver::asynNDArrayDriver(const char *portName, int maxAddr, int numP createParam(NDFileCaptureString, asynParamInt32, &NDFileCapture); createParam(NDFileDeleteDriverFileString, asynParamInt32, &NDFileDeleteDriverFile); createParam(NDFileLazyOpenString, asynParamInt32, &NDFileLazyOpen); + createParam(NDFileCreateDirString, asynParamInt32, &NDFileCreateDir); createParam(NDFileTempSuffixString, asynParamOctet, &NDFileTempSuffix); createParam(NDAttributesFileString, asynParamOctet, &NDAttributesFile); createParam(NDArrayDataString, asynParamGenericPointer, &NDArrayData); @@ -586,6 +587,7 @@ asynNDArrayDriver::asynNDArrayDriver(const char *portName, int maxAddr, int numP setIntegerParam(NDAutoIncrement, 0); setStringParam (NDFileTemplate, "%s%s_%3.3d.dat"); setIntegerParam(NDFileNumCaptured, 0); + setIntegerParam(NDFileCreateDir, 0); setStringParam (NDFileTempSuffix, ""); setIntegerParam(NDPoolMaxBuffers, this->pNDArrayPool->maxBuffers()); diff --git a/ADApp/ADSrc/asynNDArrayDriver.h b/ADApp/ADSrc/asynNDArrayDriver.h index 9a7e83ff8..6579d14e6 100755 --- a/ADApp/ADSrc/asynNDArrayDriver.h +++ b/ADApp/ADSrc/asynNDArrayDriver.h @@ -72,9 +72,9 @@ typedef enum { #define NDFileCaptureString "CAPTURE" /**< (asynInt32, r/w) Start or stop capturing arrays */ #define NDFileDeleteDriverFileString "DELETE_DRIVER_FILE" /**< (asynInt32, r/w) Delete driver file */ #define NDFileLazyOpenString "FILE_LAZY_OPEN" /**< (asynInt32, r/w) Don't open file until first frame arrives in Stream mode */ +#define NDFileCreateDirString "CREATE_DIR" /**< (asynInt32, r/w) If set then create the target directory before writing file */ #define NDFileTempSuffixString "FILE_TEMP_SUFFIX" /**< (asynOctet, r/w) Temporary filename suffix while writing data to file. The file will be renamed (suffix removed) upon closing the file. */ - #define NDAttributesFileString "ND_ATTRIBUTES_FILE" /**< (asynOctet, r/w) Attributes file name */ /* The detector array data */ @@ -151,6 +151,7 @@ class epicsShareFunc asynNDArrayDriver : public asynPortDriver { int NDFileCapture; int NDFileDeleteDriverFile; int NDFileLazyOpen; + int NDFileCreateDir; int NDFileTempSuffix; int NDAttributesFile; int NDArrayData; diff --git a/ADApp/Db/NDFile.template b/ADApp/Db/NDFile.template index 8214d6524..85eec4867 100644 --- a/ADApp/Db/NDFile.template +++ b/ADApp/Db/NDFile.template @@ -37,6 +37,23 @@ record(bi, "$(P)$(R)FilePathExists_RBV") field(SCAN, "I/O Intr") } +record(longout, "$(P)$(R)CreateDirectory") +{ + field(PINI, "YES") + field(DTYP, "asynInt32") + field(OUT, "@asyn($(PORT),$(ADDR),$(TIMEOUT))CREATE_DIR") + field(VAL, "0" ) + info(autosaveFields, "VAL") +} + +record(longin, "$(P)$(R)CreateDirectory_RBV") +{ + field(DTYP, "asynInt32") + field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))CREATE_DIR") + field(VAL, "") + field(SCAN, "I/O Intr") +} + # Filename record(waveform, "$(P)$(R)FileName") { diff --git a/ADApp/Db/NDFile_settings.req b/ADApp/Db/NDFile_settings.req index 52b05edc0..8587d44d0 100644 --- a/ADApp/Db/NDFile_settings.req +++ b/ADApp/Db/NDFile_settings.req @@ -8,3 +8,4 @@ $(P)$(R)FileFormat $(P)$(R)FileWriteMode $(P)$(R)NumCapture $(P)$(R)DeleteDriverFile +$(P)$(R)CreateDirectory diff --git a/ADApp/pluginSrc/NDPluginFile.cpp b/ADApp/pluginSrc/NDPluginFile.cpp index 00cc372d5..fc8edd942 100644 --- a/ADApp/pluginSrc/NDPluginFile.cpp +++ b/ADApp/pluginSrc/NDPluginFile.cpp @@ -31,6 +31,100 @@ static const char *driverName="NDPluginFile"; + +/** + * Make sure all the directories in the create path exist. Returns asnySuccess + * or asynFail in the hope that this is propagated back to GDA. + */ +#define MAX_PATH_PARTS 32 + +#if defined(_WIN32) +#include +#define strtok_r(a,b,c) strtok(a,b) +#define mkdir(a,b) _mkdir(a) +#define delim "\\" +#else +#include +#include +#define delim "/" +#endif + +/** Function to create a directory path for a file. + \param[in] createPath Path to create. The final part is the file name and is not created. + \param[in] stem_size This determines how much file stem to assume exists before attempting + to create directories: + stem_size = 0 create no directories + stem_size = 1 create all directories needed (i.e. only assume root directory exists). + stem_size = 2 Assume 1 directory below the root directory exists + stem_size = -1 Assume all but one direcory exists + stem_size = -2 Assume all but two directories exist. +*/ +asynStatus NDPluginFile::createDirectoryPath( char *createPath, int stem_size ) +{ + asynStatus result = asynSuccess; + char * parts[MAX_PATH_PARTS]; + int num_parts; + char directory[MAX_FILENAME_LEN]; + + // Initialise the directory to create + char nextDir[MAX_FILENAME_LEN]; + + // Extract the next name from the directory + char* saveptr; + int i=0; + + // Check for trivial case. + if (stem_size == 0) return asynSuccess; + + // Check for Windows disk designator + if ( createPath[1] == ':' ) + { + nextDir[0]=createPath[0]; + nextDir[1]=':'; + i+=2; + } + + // Skip over any more delimiters + while ( (createPath[i] == '/' || createPath[i] == '\\') && i < MAX_FILENAME_LEN) + { + nextDir[i] = createPath[i]; + ++i; + } + nextDir[i] = 0; + + // Now, tokenise the path - first making a copy because strtok is destructive + strcpy( directory, &createPath[i] ); + num_parts = 0; + parts[num_parts] = strtok_r( directory, "\\/",&saveptr); + while ( parts[num_parts] != NULL ) { + parts[++num_parts] = strtok_r(NULL, "\\/",&saveptr); + } + + // Handle the case if the stem size is negative + if (stem_size < 0) + { + stem_size = num_parts + stem_size; + if (stem_size < 1) stem_size = 1; + } + + // Loop through parts creating dirctories + for ( i = 1; i < num_parts && result != asynError; i++ ) + { + strcat(nextDir, parts[i-1]); + + if ( i >= stem_size ) + { + if(mkdir(nextDir, 0777) != 0 && errno != EEXIST) + { + result = asynError; + } + } + strcat(nextDir, delim); + } + + return result; +} + /** Base method for opening a file * Creates the file name with NDPluginBase::createFileName, then calls the pure virtual function openFile * in the derived class. */ @@ -58,6 +152,11 @@ asynStatus NDPluginFile::openFileBase(NDFileOpenMode_t openMode, NDArray *pArray return(status); } setStringParam(NDFullFileName, fullFileName); + + int createDirectory; + getIntegerParam( NDFileCreateDir, &createDirectory ); + if (createDirectory) + status = createDirectoryPath( fullFileName, createDirectory ); getStringParam(NDFileTempSuffix, sizeof(tempSuffix), tempSuffix); if ( *tempSuffix != 0 && @@ -649,8 +748,9 @@ bool NDPluginFile::isFrameValid(NDArray *pArray) // Check frame size in X and Y dimensions if ((initInfo->xSize != info.xSize) || (initInfo->ySize != info.ySize)) { asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, - "NDPluginFile::isFrameValid: WARNING: Frame dimensions have changed X:%ld,%ld Y:%ld,%ld]\n", - (long)initInfo->xSize, (long)info.xSize, (long)initInfo->ySize, (long)info.ySize); + "NDPluginFile::isFrameValid: WARNING: Frame dimensions have changed X:%lu,%lu Y:%lu,%lu]\n", + (unsigned long)initInfo->xSize, (unsigned long)info.xSize, + (unsigned long)initInfo->ySize, (unsigned long)info.ySize); valid = false; } diff --git a/RELEASE.md b/RELEASE.md index e3d49521a..7a97a08e2 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -48,11 +48,14 @@ R2-2 (January XXX, 2015) constraints no longer apply. ### NDPluginFile +* Created the NDFileCreateDir parameter. This allows file writers to create a controlled number + of directories in the path of the output file. * Added the NDFileTempSuffix string parameter. When this string is not empty it will be used as a temporary suffix on the output filename while the file is being written. When writing is complete, the file is closed and then renamed to have the suffix removed. The rename operation is atomic from a filesystem view and can be used by monitoring applications like rsync or inotify to kick off processing applications. +>>>>>>> master ### NDFileHDF5 * Created separated NDFileHDF5.h so class can be exported to other applications. diff --git a/documentation/areaDetectorDoc.html b/documentation/areaDetectorDoc.html index 30d711806..612ecb67a 100755 --- a/documentation/areaDetectorDoc.html +++ b/documentation/areaDetectorDoc.html @@ -961,6 +961,31 @@

bo
bi + + + NDFileCreateDir + + asynInt32 + + r/w + + This parameter is used to automatically create directories if they don't exist. + If it is zero (default), no directories are created. If it is negative, then the + absolute value is the maximum of directories that will be created (i.e. -1 will + create a maximum of one directory to complete the path, -2 will create a maximum + of 2 directories). If it is positive, then at least that many directories in the + path must exist (i.e. a value of 1 will create all directories below the root + directory and 2 will not create a directory in the root directory). + + + CREATE_DIR + + $(P)$(R)CreateDirectory
+ $(P)$(R)CreateDirectory_RBV + + longout
+ longin + NDFileTempSuffix