IPP Software Navigation Tools IPP Links Communication Pan-STARRS Links

Ignore:
Timestamp:
Mar 10, 2006, 5:20:10 PM (20 years ago)
Author:
Paul Price
Message:

Changing concepts to only get stuff as the source becomes available; nearly done

File:
1 edited

Legend:

Unmodified
Added
Removed
  • branches/rel10_ifa/psModules/src/astrom/pmConceptsRead.c

    r6552 r6570  
    55#include "pmFPA.h"
    66#include "pmConceptsRead.h"
    7 #include "psAdditionals.h"
    8 
     7
     8//////////////////////////////////////////////////////////////////////////////////////////////////////////////
     9// File-static functions
     10//////////////////////////////////////////////////////////////////////////////////////////////////////////////
     11
     12
     13static float parseF32(psMetadataItem *item
     14                     )
     15{
     16    switch (item->type) {
     17    case PS_DATA_F32:
     18        return item->data.F32;
     19    case PS_DATA_F64:
     20        // Assume it's OK to truncate to floating point from double
     21        return (float)item->data.F64;
     22    case PS_DATA_S32:
     23        // Promote to float
     24        return (float)item->data.S32;
     25    default:
     26        psError(PS_ERR_IO, true, "Concept %s (%s) is not of floating point type (%x) --- treating as NaN.\n",
     27                item->name, item->comment, item->type);
     28        return NAN;
     29    }
     30}
     31
     32static double parseF64(psMetadataItem *item
     33                      )
     34{
     35    switch (item->type) {
     36    case PS_TYPE_F64:
     37        return item->data.F64;
     38    case PS_TYPE_F32:
     39        // Promote to double
     40        return (double)item->data.F32;
     41    case PS_TYPE_S32:
     42        // Promote to double
     43        return (double)item->data.S32;
     44    default:
     45        psError(PS_ERR_IO, true, "Concept %s (%s) is not of double-precision floating point type (%x) "
     46                "--- treating as NaN.\n", item->name, item->comment, item->type);
     47        return NAN;
     48    }
     49}
     50
     51static int parseS32(psMetadataItem *item
     52                   )
     53{
     54    switch (item->type) {
     55    case PS_TYPE_S32:
     56        return item->data.S32;
     57    case PS_TYPE_F32:
     58        psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) should be S32, but is F32 --- converting.\n",
     59                 item->name, comment);
     60        return (int)item->data.F32;
     61    case PS_TYPE_F64:
     62        psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) should be S32, but is F64 --- converting.\n",
     63                 item->name, comment);
     64        return (int)item->data.F64;
     65    default:
     66        psError(PS_ERR_IO, true, "Concept %s (%s) is not of integer type (%x) --- treating as zero.\n",
     67                item->name, item->comment, item->type);
     68        return 0;
     69    }
     70}
     71
     72static psString parseString(psMetadataItem *item
     73                           )
     74{
     75    switch (item->type) {
     76    case PS_DATA_STRING:
     77        return psMemIncrRefCounter(item->data.V);
     78    case PS_DATA_F32: {
     79            psString value = NULL;    // String to return
     80            psStringAppend(&value, "%f", item->data.F32);
     81            return value;
     82        }
     83    case PS_DATA_S32: {
     84            psString value = NULL;    // String to return
     85            psStringAppend(&value, "%d", item->data.S32);
     86            return value;
     87        }
     88    default:
     89        psError(PS_ERR_IO, true, "Concept %s (%s) is not of string type (%x) --- treating as "
     90                "undefined.\n", name, item->comment, item->type);
     91        return psStringCopy("");
     92    }
     93}
     94
     95
     96// This function gets called for the really boring concepts --- where all you have to do is parse from a
     97// header or database and you don't need to muck around with conversions.  There is no similar "formatPlain",
     98// since the type is already known.
     99static psMetadataItem *parsePlain(psMetadataItem *concept, // The concept to parse
     100                                  pmConceptSpec *spec // The concept specification
     101                                 )
     102{
     103    psMetadataItem *blank = spec->blank;// The blank specification, which carries the name, comment, type
     104    switch (blank->type) {
     105    case PS_DATA_STRING: {
     106            psString string = parseString(concept); // Get the string, so I can free it after it goes on the MDI
     107            psMetadataItem *item = psMetadataItemAllocStr(blank->name, blank->comment, string);
     108            psFree(string);
     109            return item;
     110        }
     111    case PS_DATA_S32:
     112        return psMetadataItemAllocS32(blank->name, blank->comment, parseS32(concept));
     113    case PS_DATA_F32:
     114        return psMetadataItemAllocF32(blank->name, blank->comment, parseF32(concept));
     115    case PS_DATA_F64:
     116        return psMetadataItemAllocF64(blank->name, blank->comment, parseF64(concept));
     117    default:
     118        psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) is not of a standard type (%x)\n",
     119                 blank->name, blank->comment, blank->type);
     120        return NULL;
     121    }
     122}
     123
     124// Get the lowest HDU
     125static pmHDU *getLowestHDU(pmFPA *fpa, // The FPA
     126                           pmChip *chip, // The chip, or NULL
     127                           pmCell *cell // The cell, or NULL
     128                          )
     129{
     130    pmHDU *hdu = NULL;          // The HDU that's at the lowest level
     131    if (cell) {
     132        hdu = pmHDUFromCell(cell);
     133    } else if (chip) {
     134        hdu = pmHDUFromChip(chip);
     135    } else if (fpa) {
     136        hdu = pmHDUFromFPA(fpa);
     137    }
     138
     139    return hdu;
     140}
     141
     142
     143// Parse a single concept
     144static bool conceptParse(pmConceptSpec *spec, // The concept specification
     145                         psMetadataItem *concept, // The concept to parse
     146                         psMetadata *cameraFormat, // The camera format
     147                         psMetadata *target, // The target
     148                         pmFPA *fpa,    // The FPA
     149                         pmChip *chip,  // The chip
     150                         pmCell *cell   // The cell
     151                        )
     152{
     153    if (concept) {
     154        psMetadataItem *parsed = NULL;  // The parsed concept
     155        if (spec->parse) {
     156            parsed = spec->parse(concept, spec, cameraFormat, fpa, chip, cell);
     157        } else {
     158            parsed = parsePlain(concept, spec->blank);
     159        }
     160
     161        // Reformat so that everything's clean
     162        if (strcmp(spec->blank->name, parsedItem->name) != 0 ||
     163                strcmp(spec->blank->comment, parsedItem->comment) != 0) {
     164            psMetadataItem *cleaned = NULL;     // Item that's been cleaned up --- correct name and comment
     165            switch (spec->type) {
     166            case PS_DATA_STRING:
     167                cleaned = psMetadataItemAllocStr(spec->blank->name, spec->blank->comment, concept->data.V);
     168                break;
     169            case PS_DATA_S32:
     170                cleaned = psMetadataItemAllocS32(spec->blank->name, spec->blank->comment, concept->data.S32);
     171                break;
     172            case PS_DATA_F32:
     173                cleaned = psMetadataItemAllocF32(spec->blank->name, spec->blank->comment, concept->data.F32);
     174                break;
     175            case PS_DATA_F64:
     176                cleaned = psMetadataItemAllocF64(spec->blank->name, spec->blank->comment, concept->data.F64);
     177                break;
     178            default:
     179                cleaned = psMetadataItemAlloc(spec->blank->name, concept->type, spec->blank->comment,
     180                                              concept->data.V);
     181            }
     182            psFree(parsed);
     183            parsed = cleaned;
     184        }
     185        psMetadataAddItem(target, parsed, PS_LIST_TAIL, PS_META_REPLACE);
     186        psFree(parsed);     // Drop reference
     187        return true;
     188    }
     189
     190    return false;
     191}
     192
     193//////////////////////////////////////////////////////////////////////////////////////////////////////////////
     194// Public functions
     195//////////////////////////////////////////////////////////////////////////////////////////////////////////////
     196
     197bool pmConceptsReadFromCamera(psMetadata *specs, // The concept specifications
     198                              pmCell *cell,  // The cell
     199                              psMetadata *target // Place into which to read the concepts
     200                             )
     201{
     202    if (cell) {
     203        pmHDU *hdu = getLowestHDU(NULL, NULL, cell); // The HDU at the lowest level
     204        psMetadata *cameraFormat = hdu->format; // The camera format
     205        psMetadata *cellConfig = cell->config; // The camera configuration for this cell
     206        psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
     207        psMetadataItem *specItem = NULL;    // Item from the specs metadata
     208        while ((specItem = psMetadataGetAndIncrement(specsIter))) {
     209            pmConceptSpec *spec = specItem->data.V; // The specification
     210            psString name = specItem->name; // The concept name
     211            psMetadataItem *conceptItem = psMetadataLookup(cellConfig, name); // The concept, or NULL
     212            psMetadataItem *value = NULL; // The value of the concept
     213            if (conceptItem) {
     214                // Check the SOURCE
     215                psString nameSource = NULL; // String with the concept name and ".SOURCE" added
     216                psStringAppend(nameSource, "%s.SOURCE", name);
     217                bool mdok = true;       // Status of MD lookup
     218                psString source = psMetadataLookupStr(&mdok, cell->config, nameSource); // The source
     219                if (mdok && strlen(source)) {
     220                    if (strcasecmp(source, "HEADER") == 0 && conceptItem->type == PS_DATA_STRING) {
     221                        value = psMetadataLookup(hdu->header, conceptItem->data.V);
     222                    } else if (strcasecmp(source, "VALUE") == 0) {
     223                        value = psMemIncrRefCounter(conceptItem);
     224                    } else {
     225                        psError(PS_ERR_IO, true, "%s isn't HEADER or VALUE --- can't read %s\n", source,
     226                                name);
     227                        continue;
     228                    }
     229                } else {
     230                    // Assume it's specified by value
     231                    value = psMemIncrRefCounter(conceptItem);
     232                }
     233                conceptParse(spec, value, cameraFormat, target, NULL, NULL, cell);
     234            }
     235        }
     236        psFree(specsIter);
     237        return true;
     238    }
     239    return false;
     240}
     241
     242
     243bool pmConceptsReadFromDefaults(psMetadata *specs, // The concept specifications
     244                                pmFPA *fpa, // The FPA
     245                                pmChip *chip, // The chip
     246                                pmCell *cell, // The cell
     247                                psMetadata *target // Place into which to read the concepts
     248                               )
     249{
     250    bool mdok = true;               // Status of MD lookup
     251    psMetadata *defaults = psMetadataLookupMD(&mdok, cameraFormat, "DEFAULTS"); // The DEFAULTS spec
     252    if (mdok && defaults) {
     253        pmHDU *hdu = getLowestHDU(fpa, chip, cell); // The HDU at the lowest level
     254        psMetadata *cameraFormat = hdu->format; // The camera format
     255        psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
     256        psMetadataItem *specItem = NULL;    // Item from the specs metadata
     257        while ((specItem = psMetadataGetAndIncrement(specsIter))) {
     258            pmConceptSpec *spec = specItem->data.V; // The specification
     259            psString name = specItem->name; // The concept name
     260            psMetadataItem *conceptItem = psMetadataLookup(defaults, name); // The concept, or NULL
     261            conceptParse(spec, conceptItem, cameraFormat, target, fpa, chip, cell);
     262        }
     263        psFree(specsIter);
     264        return true;
     265    }
     266    return false;
     267}
     268
     269bool pmConceptsReadFromHeader(psMetadata *specs, // The concept specifications
     270                              pmFPA *fpa, // The FPA
     271                              pmChip *chip, // The chip
     272                              pmCell *cell,  // The cell
     273                              psMetadata *target // Place into which to read the concepts
     274                             )
     275{
     276    bool mdok = true;               // Status of MD lookup
     277    psMetadata *transSpec = psMetadataLookupMD(&mdok, cameraFormat, "TRANSLATION"); // The TRANSLATION spec
     278    if (mdok && transSpec) {
     279        pmHDU *hdu = getLowestHDU(fpa, chip, cell); // The HDU at the lowest level
     280        psMetadata *cameraFormat = hdu->format; // The camera format
     281        psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
     282        psMetadataItem *specItem = NULL;    // Item from the specs metadata
     283        while ((specItem = psMetadataGetAndIncrement(specsIter))) {
     284            pmConceptSpec *spec = specItem->data.V; // The specification
     285            psString name = specItem->name; // The concept name
     286            psString keywords = psMetadataLookupStr(&mdok, transSpec, name); // The FITS keywords
     287            if (mdok && strlen(keyword) > 0) {
     288                // In case there are multiple headers
     289                psList *keys = psStringSplit(keywords, " ,;"); // List of keywords
     290                psMetadataItem *headerItem = NULL; // The item, to be returned
     291                if (keys->n == 1) {
     292                    // Only one key --- proceed as usual
     293                    headerItem = psMetadataLookup(hdu->header, keywords);
     294                } else {
     295                    psListIterator *keysIter = psListIteratorAlloc(keys, PS_LIST_HEAD, false); // Iterator
     296                    psMetadataItem *key = NULL; // Item from iteration
     297                    psList *values = psListAlloc(void); // List containing the values
     298                    while ((key = psListGetAndIncrement(keysIter))) {
     299                        psMetadataItem *value = psMetadataLookup(hdu->header, key);
     300                        psListAdd(values, PS_LIST_TAIL, value);
     301                    }
     302                    psFree(keysIter);
     303                    headerItem = psMetadataItemAlloc(name, PS_DATA_LIST, specItem->comment, values);
     304                    psFree(values);
     305                }
     306                psFree(keys);
     307
     308                // This will also clean up the name
     309                conceptParse(spec, headerItem, cameraFormat, target, fpa, chip, cell);
     310            }
     311        }
     312        psFree(specsIter);
     313        return true;
     314    }
     315    return false;
     316}
     317
     318// XXX --- the below code has NOT been tested!
     319bool pmConceptsReadFromDatabase(psMetadata *specs, // The concept specifications
     320                                pmFPA *fpa, // The FPA
     321                                pmChip *chip, // The chip
     322                                pmCell *cell,  // The cell
     323                                psDB *db, // The database handle
     324                                psMetadata *target // Place into which to read the concepts
     325                               )
     326{
     327    bool mdok = true;               // Status of MD lookup
     328    psMetadata *dbSpec = psMetadataLookupMD(&mdok, cameraFormat, "DATABSE"); // The DATABASE spec
     329    if (mdok && dbSpec) {
     330        pmHDU *hdu = getLowestHDU(fpa, chip, cell); // The HDU at the lowest level
     331        psMetadata *cameraFormat = hdu->format; // The camera format
     332        psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
     333        psMetadataItem *specItem = NULL;    // Item from the specs metadata
     334        while ((specItem = psMetadataGetAndIncrement(specsIter))) {
     335            pmConceptSpec *spec = specItem->data.V; // The specification
     336            psString name = specItem->name; // The concept name
     337
     338            psMetadata *dbLookup = psMetadataLookupMD(&mdok, dbSpec, name);
     339            if (mdok && dbLookup) {
     340                const char *tableName = psMetadataLookupStr(&mdStatus, dbLookup, "TABLE"); // Table name
     341                // Names of the "where" columns
     342                const char *givenCols = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENDBCOL");
     343                // Values of the "where" columns
     344                const char *givenPS = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENPS");
     345
     346                // Now, need to get the "given"s
     347                if (strlen(givenCols) > 0 || strlen(givenPS) > 0) {
     348                    psList *cols = psStringSplit(givenCols, ",;"); // List of column names
     349                    psList *values = psStringSplit(givenPS, ",;"); // List of value names for the columns
     350                    psMetadata *selection = psMetadataAlloc(); // The stuff to select in the DB
     351                    if (cols->n != values->n) {
     352                        psLogMsg(__func__, PS_LOG_WARN,
     353                                 "The GIVENDBCOL and GIVENPS entries for %s do not have "
     354                                 "the same number of entries --- ignored.\n", concept);
     355                    } else {
     356                        // Iterators for the lists
     357                        psListIterator *colsIter = psListIteratorAlloc(cols, PS_LIST_HEAD, false);
     358                        psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false);
     359                        char *column = NULL;    // Name of the column
     360                        while ((column = psListGetAndIncrement(colsIter))) {
     361                            char *name = psListGetAndIncrement(valuesIter); // Name for the value
     362                            if (!strlen(column) || !strlen(name)) {
     363                                psLogMsg(__func__, PS_LOG_WARN, "One of the columns or value names for %s is "
     364                                         " empty --- ignored.\n", concept);
     365                            } else {
     366                                // Search for the value name
     367                                psMetadataItem *item = pmConceptReadFromHeader(fpa, chip, cell, name);
     368                                if (! item) {
     369                                    item = pmConceptReadFromDefault(fpa, chip, cell, name);
     370                                }
     371                                if (! item) {
     372                                    psLogMsg(__func__, PS_LOG_ERROR, "Unable to find the value name %s for DB"
     373                                             " lookup on %s --- ignored.\n", name, concept);
     374                                } else {
     375                                    // We need to create a new psMetadataItem.  I don't think we can't
     376                                    // simply hack the existing one, since that could conceivably cause
     377                                    // memory leaks
     378                                    psMetadataItem *newItem = psMetadataItemAlloc(concept, item->type,
     379                                                              item->comment,
     380                                                              item->data.V);
     381                                    psMetadataAddItem(selection, newItem, PS_LIST_TAIL, PS_META_REPLACE);
     382                                    psFree(newItem);
     383                                }
     384                            }
     385                            psFree(name);
     386                            psFree(column);
     387                        } // Iterating through the columns
     388                        psFree(colsIter);
     389                        psFree(valuesIter);
     390
     391                        psArray *dbResult = psDBSelectRows(db, tableName, selection, 2); // Lookup result
     392                        // Note that we use limit=2 in order to test if there are multiple rows returned
     393
     394                        psMetadataItem *conceptItem = NULL; // The final result of the DB lookup
     395                        if (dbResult->n == 0) {
     396                            psLogMsg(__func__, PS_LOG_WARN,
     397                                     "Unable to find any rows in DB for %s --- ignored\n", concept);
     398                        } else {
     399                            if (dbResult-> n > 1) {
     400                                psLogMsg(__func__, PS_LOG_WARN,
     401                                         "Multiple rows returned in DB lookup for %s --- "
     402                                         " using the first one only.\n", concept);
     403                            }
     404                            conceptItem = (psMetadataItem*)dbResult->data[0];
     405                        }
     406
     407                        // Now we have the result
     408                        conceptParse(spec, headerItem, cameraFormat, target, fpa, chip, cell);
     409
     410                    }
     411                    psFree(cols);
     412                    psFree(values);
     413                }
     414            } // Doing the "given"s.
     415
     416        } // Iterating through the concept specifications
     417        psFree(specsIter);
     418
     419        return true;
     420    }
     421    return false;
     422}
     423
     424
     425
     426
     427#if 0
    9428
    10429psMetadataItem *pmConceptReadFromCamera(pmCell *cell, // The cell
     
    241660
    242661
    243 float pmConceptReadF32(pmFPA *fpa,        // The FPA
    244                        pmChip *chip,      // The chip
    245                        pmCell *cell,      // The cell
    246                        psDB *db,          // DB handle
    247                        const char *name // Name of the concept
    248                       )
    249 {
    250     psMetadataItem *item = pmConceptRead(fpa, chip, cell, db, name);
    251     float value = NAN;
    252     if (item) {
    253         switch (item->type) {
    254         case PS_DATA_F32:
    255             value = item->data.F32;
    256             break;
    257         case PS_DATA_F64:
    258             // Assume it's OK to truncate to floating point from double
    259             value = (float)item->data.F64;
    260             break;
    261         case PS_DATA_S32:
    262             // Promote to float
    263             value = (float)item->data.S32;
    264             break;
    265         default:
    266             psError(PS_ERR_IO, true, "Concept %s (%s) is not of floating point type (%x) --- treating as "
    267                     "undefined.\n", name, item->comment, item->type);
    268         }
    269         psTrace(__func__, 7, "Adding %s (%s): %f\n", name, item->comment, value);
    270     } else {
    271         psError(PS_ERR_IO, true, "Concept %s is not defined.\n", name);
    272     }
    273 
    274     return value;
    275 }
    276 
    277 double pmConceptReadF64(pmFPA *fpa,   // The FPA
    278                         pmChip *chip, // The chip
    279                         pmCell *cell, // The cell
    280                         psDB *db,     // DB handle
    281                         const char *name // Name of the concept
    282                        )
    283 {
    284     psMetadataItem *item = pmConceptRead(fpa, chip, cell, db, name);
    285     double value = NAN;
    286     if (item) {
    287         switch (item->type) {
    288         case PS_TYPE_F64:
    289             value = item->data.F64;
    290             break;
    291         case PS_TYPE_F32:
    292             // Promote to double
    293             value = (double)item->data.F32;
    294             break;
    295         case PS_TYPE_S32:
    296             // Promote to double
    297             value = (double)item->data.S32;
    298             break;
    299         default:
    300             psError(PS_ERR_IO, true, "Concept %s (%s) is not of double-precision floating point type (%x) "
    301                     "--- treating as undefined.\n", name, item->comment, item->type);
    302         }
    303         psTrace(__func__, 7, "Adding %s (%s): %f\n", name, item->comment, value);
    304     } else {
    305         psError(PS_ERR_IO, true, "Concept %s is not defined.\n", name);
    306     }
    307 
    308     return value;
    309 }
    310 
    311 int pmConceptReadS32(pmFPA *fpa,   // The FPA
    312                      pmChip *chip, // The chip
    313                      pmCell *cell, // The cell
    314                      psDB *db,     // DB handle
    315                      const char *name // Name of the concept
    316                     )
    317 {
    318     psMetadataItem *item = pmConceptRead(fpa, chip, cell, db, name);
    319     int value = 0;
    320     if (item) {
    321         switch (item->type) {
    322         case PS_TYPE_S32:
    323             value = item->data.S32;
    324             break;
    325         case PS_TYPE_F32:
    326             psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) should be S32, but is F32 --- converting.\n",
    327                      name, comment);
    328             value = (int)item->data.F32;
    329             break;
    330         case PS_TYPE_F64:
    331             psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) should be S32, but is F64 --- converting.\n",
    332                      name, comment);
    333             value = (int)item->data.F64;
    334             break;
    335         default:
    336             psError(PS_ERR_IO, true, "Concept %s (%s) is not of integer type (%x) --- treating as "
    337                     "undefined.\n", name, item->comment, item->type);
    338         }
    339         psTrace(__func__, 7, "Read concept %s (%s): %d\n", name, item->comment, value);
    340     } else {
    341         psError(PS_ERR_IO, true, "Concept %s is not defined.\n", name);
    342     }
    343 
    344     return value;
    345 }
    346 
    347 psString pmConceptReadString(pmFPA *fpa, // The FPA
    348                              pmChip *chip, // The chip
    349                              pmCell *cell, // The cell
    350                              psDB *db,  // DB handle
    351                              const char *name // Name of the concept
    352                             )
    353 {
    354     psMetadataItem *item = pmConceptRead(fpa, chip, cell, db, name);
    355     psString value = NULL;
    356     if (item) {
    357         switch (item->type) {
    358         case PS_DATA_STRING:
    359             value = psMemIncrRefCounter(item->data.V);
    360             break;
    361         case PS_DATA_F32:
    362             psStringAppend(&value, "%f", item->data.F32);
    363             break;
    364         case PS_DATA_S32:
    365             psStringAppend(&value, "%d", item->data.S32);
    366             break;
    367         default:
    368             psError(PS_ERR_IO, true, "Concept %s (%s) is not of string type (%x) --- treating as "
    369                     "undefined.\n", name, item->comment, item->type);
    370         }
    371         psTrace(__func__, 7, "Read concept %s (%s): %s\n", name, item->comment, value);
    372     } else {
    373         psError(PS_ERR_IO, true, "Concept %s is not defined.\n", name);
    374         value = psStringCopy("");
    375     }
    376 
    377     return value;
    378 }
    379 
     662#endif
Note: See TracChangeset for help on using the changeset viewer.