Commit dbf784cf authored by Uwe Schulzweida's avatar Uwe Schulzweida
Browse files

iterator based interface for file driven input [patch 11/14 from Nathanael]

parent e1f34dcc
......@@ -233,8 +233,16 @@ src/grid.c -text
src/grid.h -text
src/ieg.h -text
src/ieglib.c -text
src/input_file.c -text
src/input_file.h -text
src/institution.c -text
src/institution.h -text
src/iterator.c -text
src/iterator.h -text
src/iterator_fallback.c -text
src/iterator_fallback.h -text
src/iterator_grib.c -text
src/iterator_grib.h -text
src/make_cdilib -text
src/make_fint.c -text
src/mo_cdi.f90 -text
......@@ -271,6 +279,8 @@ src/pkgconfig/cdi.pc.in -text
src/pkgconfig/cdipio.pc.in -text
src/proprietarySystemWorkarounds.c -text
src/proprietarySystemWorkarounds.h -text
src/referenceCounting.c -text
src/referenceCounting.h -text
src/resource_handle.c -text
src/resource_handle.h -text
src/resource_unpack.c -text
......
......@@ -56,8 +56,16 @@ libcdi_la_SOURCES = \
grid.h \
ieg.h \
ieglib.c \
input_file.c \
input_file.h \
institution.c \
institution.h \
iterator.c \
iterator.h \
iterator_fallback.c \
iterator_fallback.h \
iterator_grib.c \
iterator_grib.h \
model.c \
model.h \
namespace.c \
......@@ -66,6 +74,8 @@ libcdi_la_SOURCES = \
serialize.c \
proprietarySystemWorkarounds.c \
proprietarySystemWorkarounds.h \
referenceCounting.c \
referenceCounting.h \
resource_handle.c\
resource_handle.h\
service.h \
......
......@@ -150,10 +150,10 @@ am_libcdi_la_OBJECTS = basetime.lo binary.lo calendar.lo cdf.lo \
cdf_int.lo cdi_error.lo cdi_util.lo cdiFortran.lo \
cgribexlib.lo dmemory.lo cksum.lo cdi_cksum.lo error.lo \
extralib.lo file.lo gaussgrid.lo gribapi.lo \
gribapi_utilities.lo grid.lo ieglib.lo \
institution.lo \
model.lo namespace.lo serialize.lo \
proprietarySystemWorkarounds.lo \
gribapi_utilities.lo grid.lo ieglib.lo input_file.lo \
institution.lo iterator.lo iterator_fallback.lo \
iterator_grib.lo model.lo namespace.lo serialize.lo \
proprietarySystemWorkarounds.lo referenceCounting.lo \
resource_handle.lo servicelib.lo stream_cdf.lo \
stream_cgribex.lo stream_ext.lo stream_grb.lo \
stream_gribapi.lo stream_history.lo stream_ieg.lo \
......@@ -488,8 +488,16 @@ libcdi_la_SOURCES = \
grid.h \
ieg.h \
ieglib.c \
input_file.c \
input_file.h \
institution.c \
institution.h \
iterator.c \
iterator.h \
iterator_fallback.c \
iterator_fallback.h \
iterator_grib.c \
iterator_grib.h \
model.c \
model.h \
namespace.c \
......@@ -498,6 +506,8 @@ libcdi_la_SOURCES = \
serialize.c \
proprietarySystemWorkarounds.c \
proprietarySystemWorkarounds.h \
referenceCounting.c\
referenceCounting.h\
resource_handle.c\
resource_handle.h\
service.h \
......@@ -731,7 +741,11 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gribapi_utilities.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/grid.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ieglib.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/input_file.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/institution.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/iterator.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/iterator_fallback.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/iterator_grib.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/model.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/namespace.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pio.Plo@am__quote@
......@@ -750,6 +764,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pio_server.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pio_util.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proprietarySystemWorkarounds.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/referenceCounting.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/resource_handle.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/resource_unpack.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/serialize.Plo@am__quote@
......
......@@ -31,6 +31,7 @@ extern "C" {
/* Error identifier */
#define CDI_NOERR 0 /* No Error */
#define CDI_EEOF -1 /* The end of file was encountered */
#define CDI_ESYSTEM -10 /* Operating system error */
#define CDI_EINVAL -20 /* Invalid argument */
#define CDI_EUFTYPE -21 /* Unsupported file type */
......@@ -222,6 +223,17 @@ extern "C" {
/* number of unsigned char needed to store UUID */
#define CDI_UUID_SIZE 16
/* Structs that are used to return data to the user */
typedef struct CdiParam { int discipline, category, number; } CdiParam;
/* Opaque types */
typedef struct CdiIterator CdiIterator;
typedef struct CdiGribIterator CdiGribIterator;
/* CDI control routines */
void cdiReset(void);
......@@ -358,6 +370,62 @@ void streamCopyRecord(int streamIDdest, int streamIDsrc);
void streamInqGRIBinfo(int streamID, int *intnum, float *fltnum, off_t *bignum);
/* File driven I/O (may yield better performance than using the streamXXX functions) */
//Creation & Destruction
CdiIterator* cdiIterator_new(const char* path); //Requires a subsequent call to cdiIteratorNextField() to point the iterator at the first field.
CdiIterator* cdiIterator_clone(CdiIterator* me);
char* cdiIterator_serialize(CdiIterator* me); //Returns a malloc'ed string.
CdiIterator* cdiIterator_deserialize(const char* description); //description is a string that was returned by cdiIteratorSerialize(). Returns a copy of the original iterator.
void cdiIterator_print(CdiIterator* me, FILE* stream);
void cdiIterator_delete(CdiIterator* me);
//Advancing an iterator
int cdiIterator_nextField(CdiIterator* me); //Points the iterator at the next field, returns CDI_EEOF if there are no more fields in the file.
//Introspecting metadata
//All outXXX arguments to these functions may be NULL.
char* cdiIterator_inqStartTime(CdiIterator* me); //Returns the (start) time as an ISO-8601 coded string. The caller is responsible to free() the returned string.
char* cdiIterator_inqEndTime(CdiIterator* me); //Returns the end time of an integration period as an ISO-8601 coded string, or NULL if there is no end time. The caller is responsible to free() the returned string.
char* cdiIterator_inqVTime(CdiIterator* me); //Returns the validity date as an ISO-8601 coded string. The caller is responsible to free() the returned string.
int cdiIterator_inqLevelType(CdiIterator* me, int levelSelector, char** outName, char** outLongName, char** outStdName, char** outUnit); //callers are responsible to free() strings that they request
int cdiIterator_inqLevel(CdiIterator* me, int levelSelector, double* outValue1, double* outValue2); //outValue2 is only written to if the level is a hybrid level
int cdiIterator_inqLevelUuid(CdiIterator* me, int* outVgridNumber, int* outLevelCount, unsigned char (*outUuid)[CDI_UUID_SIZE]); //outUuid must point to a buffer of 16 bytes, returns an error code if no generalized zaxis is used.
CdiParam cdiIterator_inqParam(CdiIterator* me);
int cdiIterator_inqDatatype(CdiIterator* me);
int cdiIterator_inqTsteptype(CdiIterator* me);
char* cdiIterator_inqVariableName(CdiIterator* me); //The caller is responsible to free() the returned buffer.
int cdiIterator_inqGridId(CdiIterator* me); //The returned id is only valid until the next call to cdiIteratorNextField().
//Reading data
void cdiIterator_readField(CdiIterator* me, double* data_vec, size_t* nmiss);
void cdiIterator_readFieldF(CdiIterator* me, float* data_vec, size_t* nmiss);
//TODO[NH]: Add functions to read partial fields.
//Direct access to grib fields
CdiGribIterator* cdiGribIterator_clone(CdiIterator* me); //Returns NULL if the associated file is not a GRIB file.
void cdiGribIterator_delete(CdiGribIterator* me);
//Callthroughs to GRIB-API
int cdiGribIterator_getLong(CdiGribIterator* me, const char* key, long* value); //Same semantics as grib_get_long().
int cdiGribIterator_getDouble(CdiGribIterator* me, const char* key, double* value); //Same semantics as grib_get_double().
int cdiGribIterator_getLength(CdiGribIterator* me, const char* key, size_t* value); //Same semantics as grib_get_length().
int cdiGribIterator_getString(CdiGribIterator* me, const char* key, char* value, size_t* length); //Same semantics as grib_get_string().
int cdiGribIterator_getSize(CdiGribIterator* me, const char* key, size_t* value); //Same semantics as grib_get_size().
int cdiGribIterator_getLongArray(CdiGribIterator* me, const char* key, long* value, size_t* array_size); //Same semantics as grib_get_long_array().
int cdiGribIterator_getDoubleArray(CdiGribIterator* me, const char* key, double* value, size_t* array_size); //Same semantics as grib_get_double_array().
//Convenience functions for accessing GRIB-API keys
int cdiGribIterator_inqEdition(CdiGribIterator* me);
long cdiGribIterator_inqLongValue(CdiGribIterator* me, const char* key); //Aborts on failure to fetch the given key.
long cdiGribIterator_inqLongDefaultValue(CdiGribIterator* me, const char* key, long defaultValue); //Returns the default value if the given key is not present.
double cdiGribIterator_inqDoubleValue(CdiGribIterator* me, const char* key); //Aborts on failure to fetch the given key.
double cdiGribIterator_inqDoubleDefaultValue(CdiGribIterator* me, const char* key, double defaultValue); //Returns the default value if the given key is not present.
char* cdiGribIterator_inqStringValue(CdiGribIterator* me, const char* key); //Returns a malloc'ed string.
/* VLIST routines */
/* vlistCreate: Create a variable list */
......
......@@ -31,6 +31,8 @@
!
INTEGER CDI_NOERR
PARAMETER (CDI_NOERR = 0)
INTEGER CDI_EEOF
PARAMETER (CDI_EEOF = -1)
INTEGER CDI_ESYSTEM
PARAMETER (CDI_ESYSTEM = -10)
INTEGER CDI_EINVAL
......@@ -377,6 +379,12 @@
INTEGER CDI_UUID_SIZE
PARAMETER (CDI_UUID_SIZE = 16)
!
! Structs that are used to return data to the user
!
!
! Opaque types
!
!
! CDI control routines
!
! cdiReset
......@@ -691,6 +699,9 @@
! INTEGER streamIDsrc)
EXTERNAL streamCopyRecord
!
! File driven I/O (may yield better performance than using the streamXXX functions)
!
!
! VLIST routines
!
......@@ -1958,6 +1969,15 @@
! (INTEGER zaxisID)
EXTERNAL zaxisInqLtype
! zaxisDefLtype2
! (INTEGER zaxisID,
! INTEGER ltype)
EXTERNAL zaxisDefLtype2
INTEGER zaxisInqLtype2
! (INTEGER zaxisID)
EXTERNAL zaxisInqLtype2
! zaxisDefVct
! (INTEGER zaxisID,
! INTEGER size,
......
......@@ -60,6 +60,12 @@
/* number of unsigned char needed to store UUID */
/* Structs that are used to return data to the user */
/* Opaque types */
/* CDI control routines */
FCALLSCSUB0 (cdiReset, CDIRESET, cdireset)
......@@ -143,6 +149,9 @@ FCALLSCSUB3 (streamWriteRecordF, STREAMWRITERECORDF, streamwriterecordf, INT, PF
FCALLSCSUB3 (streamReadRecord, STREAMREADRECORD, streamreadrecord, INT, PDOUBLE, PINT)
FCALLSCSUB2 (streamCopyRecord, STREAMCOPYRECORD, streamcopyrecord, INT, INT)
/* File driven I/O (may yield better performance than using the streamXXX functions) */
/* VLIST routines */
FCALLSCFUN0 (INT, vlistCreate, VLISTCREATE, vlistcreate)
......@@ -412,6 +421,8 @@ FCALLSCSUB2 (zaxisDefPositive, ZAXISDEFPOSITIVE, zaxisdefpositive, INT, INT)
FCALLSCFUN1 (INT, zaxisInqPositive, ZAXISINQPOSITIVE, zaxisinqpositive, INT)
FCALLSCSUB2 (zaxisDefLtype, ZAXISDEFLTYPE, zaxisdefltype, INT, INT)
FCALLSCFUN1 (INT, zaxisInqLtype, ZAXISINQLTYPE, zaxisinqltype, INT)
FCALLSCSUB2 (zaxisDefLtype2, ZAXISDEFLTYPE2, zaxisdefltype2, INT, INT)
FCALLSCFUN1 (INT, zaxisInqLtype2, ZAXISINQLTYPE2, zaxisinqltype2, INT)
FCALLSCSUB3 (zaxisDefVct, ZAXISDEFVCT, zaxisdefvct, INT, INT, PDOUBLE)
FCALLSCSUB2 (zaxisInqVct, ZAXISINQVCT, zaxisinqvct, INT, PDOUBLE)
FCALLSCFUN1 (INT, zaxisInqVctSize, ZAXISINQVCTSIZE, zaxisinqvctsize, INT)
......
#include "input_file.h"
#include "cdi.h"
#include "dmemory.h"
#include "proprietarySystemWorkarounds.h"
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
static void cdiInputFile_destruct(CdiInputFile* me);
//For an explanation of the condestruct() pattern, see the comment in iterator_grib.c
//path != NULL -> construction
//path = NULL -> destruction
static CdiInputFile* condestruct(CdiInputFile* me, const char* path)
{
#define super() (&me->super)
if(!path) goto destruct;
cdiRefObject_construct(super());
me->path = myStrDup(path);
if(!me->path) goto destructSuper;
do
{
me->fileDescriptor = open(me->path, O_RDONLY);
}
while(me->fileDescriptor == -1 && (errno == EINTR || errno == EAGAIN));
if(me->fileDescriptor == -1) goto freePath;
//construction successfull, now we can set our own destructor
super()->destructor = (void(*)(CdiReferencedObject*))cdiInputFile_destruct;
goto success;
// ^ constructor code ^
// | |
// v destructor/error-cleanup code v
destruct:
close(me->fileDescriptor);
freePath:
free(me->path);
destructSuper:
cdiRefObject_destruct(super());
me = NULL;
success:
return me;
#undef super
}
static CdiInputFile** openFileList = NULL;
static size_t openFileCount = 0, openFileListSize = 0;
static pthread_mutex_t openFileListLock = PTHREAD_MUTEX_INITIALIZER;
//This either returns a new object, or retains and returns a preexisting open file.
CdiInputFile* cdiInputFile_make(const char* path)
{
CdiInputFile* result = NULL;
xassert(path);
int error = pthread_mutex_lock(&openFileListLock);
xassert(!error);
{
//Check the list of open files for the given path.
for(size_t i = openFileCount; i-- && !result; )
{
if(!strcmp(path, openFileList[i]->path)) result = openFileList[i];
}
//If no open file was found, we open one, otherwise we just retain the existing one one more time.
if(result)
{
cdiRefObject_retain(&result->super);
}
else
{
result = xmalloc(sizeof(*result));
if(!condestruct(result, path))
{
//An error occured during construction, avoid a memory leak.
free(result);
result = NULL;
}
else
{
//Add the new file to the list of open files.
if(openFileCount == openFileListSize)
{
openFileListSize *= 2;
if(openFileListSize < 16) openFileListSize = 16;
openFileList = xrealloc(openFileList, openFileListSize);
}
xassert(openFileCount < openFileListSize);
openFileList[openFileCount++] = result;
}
}
}
error = pthread_mutex_unlock(&openFileListLock);
xassert(!error);
return result;
}
int cdiInputFile_read(const CdiInputFile* me, off_t readPosition, size_t readSize, size_t* outActualReadSize, void* buffer)
{
char* byteBuffer = buffer;
size_t trash;
if(!outActualReadSize) outActualReadSize = &trash;
*outActualReadSize = 0;
while(readSize)
{
ssize_t bytesRead = pread(me->fileDescriptor, byteBuffer, readSize, readPosition);
if(bytesRead == -1) return (errno == EINVAL) ? CDI_EINVAL : CDI_ESYSTEM;
if(bytesRead == 0) return CDI_EEOF;
byteBuffer += bytesRead;
readPosition += bytesRead;
readSize -= bytesRead;
*outActualReadSize += bytesRead;
}
return CDI_NOERR;
}
char* cdiInputFile_copyPath(const CdiInputFile* me)
{
return myStrDup(me->path);
}
void cdiInputFile_destruct(CdiInputFile* me)
{
int error = pthread_mutex_lock(&openFileListLock);
xassert(!error);
{
//Find the position of me in the list of open files.
ssize_t position;
for(position = openFileCount; position--; ) if(openFileList[position] == me) break;
xassert(position != -1);
//Remove me from the list
openFileList[position] = openFileList[--openFileCount];
}
error = pthread_mutex_unlock(&openFileListLock);
xassert(!error);
condestruct(me, NULL);
}
#ifndef INCLUDE_GUARD_CDI_GRIB_FILE_H
#define INCLUDE_GUARD_CDI_GRIB_FILE_H
#include "referenceCounting.h"
/*
CdiInputFile is a file abstraction that allows accessing an input file through any number of channels:
It is reference counted, so that it is closed at the right place,
and it is stateless, so that accesses from different callers cannot interfere with each other.
Once the reference counting code is threadsafe, CdiInputFile will also be threadsafe.
*/
typedef struct CdiInputFile {
//public:
CdiReferencedObject super;
//private:
char* path;
int fileDescriptor;
} CdiInputFile;
//Final class, the constructor is private and not defined here.
CdiInputFile* cdiInputFile_make(const char* path); //The caller is responsible to call cdiRefObject_release() on the returned object.
int cdiInputFile_read(const CdiInputFile* me, off_t readPosition, size_t readSize, size_t* outActualReadSize, void* buffer); //Returns one of CDI_EINVAL, CDI_ESYSTEM, CDI_EEOF, OR CDI_NOERR.
char* cdiInputFile_copyPath(const CdiInputFile* me); //Returns a malloc'ed string, don't forget to free() it.
//Destructor is private as well.
#endif
This diff is collapsed.
/*
* This file is for the use of iterator.c and the CdiIterator subclasses only.
*/
#ifndef INCLUDE_GUARD_CDI_ITERATOR_INT_H
#define INCLUDE_GUARD_CDI_ITERATOR_INT_H
#include "cdi.h"
#include <stdbool.h>
/*
class CdiIterator
An iterator is an object that identifies the position of one record in a file, where a record is defined as the data belonging to one level, timestep, and variable.
Using iterators to read a file can be significantly faster than using streams, because they can avoid building an index of the file.
For file formats like grib that do not provide an index within the file, this makes the difference between reading the file once or reading the file twice.
CdiIterator is an abstract base class. Which derived class is used depends on the type of the file. The class hierarchy currently looks like this:
CdiIterator <|--+-- CdiFallbackIterator
|
+-- CdiGribIterator
The fallback implementation currently uses the stream interface of CDI under the hood to provide full functionality for all filetypes for which no iterator implementation exists yet.
*/
//TODO[NH]: Debug messages, print function.
struct CdiIterator {
int filetype; //This is used to dispatch calls to the correct subclass.
bool isAdvanced; //Used to catch inquiries before the first call to CdiIteratorNextField(). //XXX: Advanced is probably not a good word (initialized?)
//The metadata that can be accessed by the inquiry calls.
//While theoretically redundant, these fields allow the handling of most inquiry calls within the base class.
//Only the name is excempted because it needs an allocation.
//These fields are set by the subclasses in the xxxIterNextField() method.
int datatype, timesteptype;
int gridId;
CdiParam param;
//The status information for reading/advancing is added in the subclasses.
};
void baseIterConstruct(CdiIterator* me, int filetype);
const char* baseIter_constructFromString(CdiIterator* me, const char* description); //Returns a pointer past the end of the parsed portion of the description string.
void baseIterDestruct(CdiIterator* me);
#endif
#include "iterator_fallback.h"
#include "cdi.h"
#include "cdi_int.h"
#include "dmemory.h"
#include "proprietarySystemWorkarounds.h"
#include "vlist.h" //Required for vlist_t, which we require because there is no safe function available to access a variable name.
#include <assert.h>
#include <stdlib.h>
//For more information on the condestruct() pattern, see comment in src/iterator_grib.c
static CdiFallbackIterator* condestruct(CdiFallbackIterator* me, const char* path, int filetype)
{
if(me) goto destruct;
me = xmalloc(sizeof(*me));
baseIterConstruct(&me->super, filetype);
me->streamId = streamOpenRead(path);
if(me->streamId == CDI_UNDEFID) goto destructSuper;
me->vlistId = streamInqVlist(me->streamId);
if(me->vlistId == CDI_UNDEFID) goto closeStream;
me->variableCount = vlistNvars(me->vlistId);
if(me->variableCount <= 0) goto closeStream;
me->curLevelCount = -1; //Will be set in cdiFallbackIterator_nextField()
//These values are chosen so that the natural increment at the start of cdiFallbackIterator_nextField() will correctly position us at the first slice.
me->curTimestep = 0;
if(streamInqTimestep(me->streamId, me->curTimestep) <= 0) goto closeStream;
me->curVariable = 0;
me->curLevel = -1;
me->path = myStrDup(path);
if(!me->path) goto closeStream;
return me;
// ^ constructor code ^
// | |
// v destructor/error-cleanup code v
destruct:
free(me->path);
closeStream:
streamClose(me->streamId);
destructSuper:
baseIterDestruct(&me->super);
free(me);
return NULL;
}
CdiIterator* cdiFallbackIterator_new(const char* path, int filetype)
{
return &condestruct(NULL, path, filetype)->super;
}
//Fetches the info that is published by the variables in the base class from the current field.
static void fetchSuperInfo(CdiFallbackIterator* me)
{
me->super.datatype = vlistInqVarDatatype(me->vlistId, me->curVariable);
me->super.timesteptype = vlistInqVarTsteptype(me->vlistId, me->curVariable);
me->super.gridId = vlistInqVarGrid(me->vlistId, me->curVariable);
int param = vlistInqVarParam(me->vlistId, me->curVariable);
cdiDecodeParam(param, &me->super.param.number, &me->super.param.category, &me->super.param.discipline);
}
CdiFallbackIterator* cdiFallbackIterator_clone(CdiIterator* super)
{
CdiFallbackIterator* me = (CdiFallbackIterator*)super;
//Make another stream for this file. This yields an unadvanced iterator.
CdiFallbackIterator* clone = condestruct(NULL, me->path, me->super.filetype);
if(!clone) return NULL;
//Point the clone to the same position in the file.
clone->variableCount = me->variableCount;
clone->curVariable = me->curVariable;
clone->curLevelCount = me->curLevelCount;
clone->curLevel = me->curLevel;
clone->curTimestep = me->curTimestep;
clone->super.isAdvanced = super->isAdvanced;
if(super->isAdvanced) fetchSuperInfo(clone);
return clone;
}
char* cdiFallbackIterator_serialize(CdiIterator* super)
{
CdiFallbackIterator* me = (CdiFallbackIterator*)super;
char* escapedPath = cdiEscapeSpaces(me->path);
char* result = myAsprintf("%s %d %d %d %d %d", escapedPath, me->variableCount, me->curVariable, me->curLevelCount, me->curLevel, me->curTimestep);
free(escapedPath);
return result;
}
CdiFallbackIterator* cdiFallbackIterator_deserialize(const char* description)
{
CdiFallbackIterator* me = xmalloc(sizeof(*me));
if(!me) goto fail;
description = baseIter_constructFromString(&me->super, description);
while(*description == ' ') description++;
me->path = cdiUnescapeSpaces(description, &description);
if(!me->path) goto destructSuper;
me->streamId = streamOpenRead(me->path);
if(me->streamId == CDI_UNDEFID) goto freePath;
me->vlistId = streamInqVlist(me->streamId);
if(me->vlistId == CDI_UNDEFID) goto closeStream;
//This reads one variable from the description string, does error checking, and advances the given string pointer.
#define decodeValue(variable, description) do \
{ \
const char* savedStart = description; \
long long decodedValue = strtoll(description, (char**)&description, 0); /*The cast is a workaround for the wrong signature of strtoll().*/ \
variable = (int)decodedValue; \
if(savedStart == description) goto closeStream; \
if((long long)decodedValue != (long long)variable) goto closeStream; \
} while(0)
decodeValue(me->variableCount, description);
decodeValue(me->curVariable, description);
decodeValue(me->curLevelCount, description);