text: /******************************************************************************
   *
   * Project:  OpenGIS Simple Features Reference Implementation
   * Purpose:  OGR C API "Spy"
   * Author:   Even Rouault, even.rouault at spatialys.com
   *
   ******************************************************************************
   * Copyright (c) 2014, Even Rouault <even.rouault at spatialys.com>
   *
   * Permission is hereby granted, free of charge, to any person obtaining a
   * copy of this software and associated documentation files (the "Software"),
   * to deal in the Software without restriction, including without limitation
   * the rights to use, copy, modify, merge, publish, distribute, sublicense,
   * and/or sell copies of the Software, and to permit persons to whom the
   * Software is furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included
   * in all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
   * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   * DEALINGS IN THE SOFTWARE.
   ****************************************************************************/
  
  #include "cpl_port.h"
  #include "cpl_multiproc.h"
  #include "ograpispy.h"
  
  #include <cstdio>
  #include <map>
  #include <set>
  
  #include "cpl_string.h"
  #include "gdal.h"
  #include "ogr_geometry.h"
  #include "ogr_feature.h"
  #include "ogr_spatialref.h"
  #include "ogrsf_frmts.h"
  
  CPL_CVSID("$Id$")
  
  #ifdef OGRAPISPY_ENABLED
  
  int bOGRAPISpyEnabled = FALSE;
  static CPLMutex* hMutex = nullptr;
  static CPLString osSnapshotPath;
  static CPLString osSpyFile;
  static FILE* fpSpyFile = nullptr;
  extern "C" int CPL_DLL GDALIsInGlobalDestructor(void);
  
  // Keep in sync with cpl_conv.cpp
  void OGRAPISPYCPLSetConfigOption(const char*, const char*);
  void OGRAPISPYCPLSetThreadLocalConfigOption(const char*, const char*);
  
  namespace
  {
  
  class LayerDescription
  {
    public:
      int iLayer;
  
      LayerDescription(): iLayer(-1) {}
      explicit LayerDescription( int iLayerIn ): iLayer(iLayerIn) {}
  };
  
  class DatasetDescription
  {
    public:
      int iDS;
      std::map<OGRLayerH, LayerDescription> oMapLayer;
  
      DatasetDescription() : iDS(-1) {}
      explicit DatasetDescription( int iDSIn ) : iDS(iDSIn) {}
      ~DatasetDescription();
  };
  
  class FeatureDefnDescription
  {
    public:
      OGRFeatureDefnH hFDefn;
      int iUniqueNumber;
      std::map<OGRFieldDefnH, int> oMapFieldDefn;
      std::map<OGRGeomFieldDefnH, int> oMapGeomFieldDefn;
  
      FeatureDefnDescription(): hFDefn(nullptr), iUniqueNumber(-1) {}
      FeatureDefnDescription( OGRFeatureDefnH hFDefnIn, int iUniqueNumberIn ):
          hFDefn(hFDefnIn), iUniqueNumber(iUniqueNumberIn) {}
      void Free();
  };
  
  }  // namespace
  
  static std::map<OGRDataSourceH, DatasetDescription> oMapDS;
  static std::set<int> oSetDSIndex;
  static std::map<OGRLayerH, CPLString> oGlobalMapLayer;
  static OGRLayerH hLayerGetNextFeature = nullptr;
  static OGRLayerH hLayerGetLayerDefn = nullptr;
  static bool bDeferGetFieldCount = false;
  static int nGetNextFeatureCalls = 0;
  static std::set<CPLString> aoSetCreatedDS;
  static std::map<OGRFeatureDefnH, FeatureDefnDescription> oMapFDefn;
  static std::map<OGRGeomFieldDefnH, CPLString> oGlobalMapGeomFieldDefn;
  static std::map<OGRFieldDefnH, CPLString> oGlobalMapFieldDefn;
  
  void FeatureDefnDescription::Free()
  {
      {
          std::map<OGRGeomFieldDefnH, int>::iterator oIter =
              oMapGeomFieldDefn.begin();
          for( ; oIter != oMapGeomFieldDefn.end(); ++oIter )
              oGlobalMapGeomFieldDefn.erase(oIter->first);
      }
      {
          std::map<OGRFieldDefnH, int>::iterator oIter =
              oMapFieldDefn.begin();
          for( ; oIter != oMapFieldDefn.end(); ++oIter )
              oGlobalMapFieldDefn.erase(oIter->first);
      }
  }
  
  DatasetDescription::~DatasetDescription()
  {
      std::map<OGRLayerH, LayerDescription>::iterator oIter =
          oMapLayer.begin();
      for( ; oIter != oMapLayer.end(); ++oIter )
          oGlobalMapLayer.erase(oIter->first);
  }
  
  void OGRAPISpyDestroyMutex()
  {
      if( hMutex )
      {
          CPLDestroyMutex(hMutex);
          hMutex = nullptr;
      }
  }
  
  static void OGRAPISpyFileReopen()
  {
      if( fpSpyFile == nullptr )
      {
          fpSpyFile = fopen(osSpyFile, "ab");
          if( fpSpyFile == nullptr )
              fpSpyFile = stderr;
      }
  }
  
  static void OGRAPISpyFileClose()
  {
      if( fpSpyFile != stdout && fpSpyFile != stderr )
      {
          fclose(fpSpyFile);
          fpSpyFile = nullptr;
      }
  }
  
  static bool OGRAPISpyEnabled()
  {
      const char* pszSpyFile = CPLGetConfigOption("OGR_API_SPY_FILE", nullptr);
      bOGRAPISpyEnabled = pszSpyFile != nullptr;
      if( !bOGRAPISpyEnabled )
      {
          osSpyFile.resize(0);
          aoSetCreatedDS.clear();
          return false;
      }
      if( !osSpyFile.empty() )
          return true;
  
      CPLMutexHolderD(&hMutex);
      if( !osSpyFile.empty() )
          return true;
  
      osSpyFile = pszSpyFile;
  
      const char* pszSnapshotPath =
          CPLGetConfigOption("OGR_API_SPY_SNAPSHOT_PATH", ".");
      if( EQUAL(pszSnapshotPath, "NO") )
          osSnapshotPath = "";
      else
          osSnapshotPath = pszSnapshotPath;
  
      if( EQUAL(pszSpyFile, "stdout") )
          fpSpyFile = stdout;
      else if( EQUAL(pszSpyFile, "stderr") )
          fpSpyFile = stderr;
      else
          fpSpyFile = fopen(pszSpyFile, "wb");
      if( fpSpyFile == nullptr )
          fpSpyFile = stderr;
  
      fprintf(fpSpyFile,
              "# This file is generated by the OGR_API_SPY mechanism.\n");
      fprintf(fpSpyFile, "from osgeo import gdal\n");
      fprintf(fpSpyFile, "from osgeo import ogr\n");
      fprintf(fpSpyFile, "from osgeo import osr\n");
      fprintf(fpSpyFile, "import os\n");
      fprintf(fpSpyFile, "import shutil\n");
      // To make pyflakes happy in case it is unused later.
      fprintf(fpSpyFile, "os.access\n");
      fprintf(fpSpyFile, "shutil.copy\n");  // Same here.
      fprintf(fpSpyFile, "\n");
  
      return true;
  }
  
  static CPLString OGRAPISpyGetOptions( char** papszOptions )
  {
      if( papszOptions == nullptr )
      {
          return "[]";
      }
  
      CPLString options = "[";
      for( char** papszIter = papszOptions; *papszIter != nullptr; papszIter++ )
      {
          if( papszIter != papszOptions )
              options += ", ";
          options += "'";
          options += *papszIter;
          options += "'";
      }
      options += "]";
  
      return options;
  }
  
  static CPLString OGRAPISpyGetString( const char* pszStr )
  {
      if( pszStr == nullptr )
          return "None";
      CPLString osRet = "'";
      while( *pszStr )
      {
          if( *pszStr == '\'' )
              osRet += "\\'";
          else if( *pszStr == '\\' )
              osRet += "\\\\";
          else
              osRet += *pszStr;
          pszStr++;
      }
      osRet += "'";
      return osRet;
  }
  
  static CPLString OGRAPISpyGetDSVar( OGRDataSourceH hDS )
  {
      if( hDS && oMapDS.find(hDS) == oMapDS.end() )
      {
          int i = 1;
          while( oSetDSIndex.find(i) != oSetDSIndex.end() )
              i ++;
          oMapDS[hDS] = DatasetDescription(i);
          oSetDSIndex.insert(i);
      }
      return CPLSPrintf("ds%d", hDS ? oMapDS[hDS].iDS : 0);
  }
  
  static CPLString OGRAPISpyGetLayerVar( OGRLayerH hLayer )
  {
      return oGlobalMapLayer[hLayer];
  }
  
  static CPLString OGRAPISpyGetAndRegisterLayerVar( OGRDataSourceH hDS,
                                                    OGRLayerH hLayer )
  {
      DatasetDescription& dd = oMapDS[hDS];
      if( hLayer && dd.oMapLayer.find(hLayer) == dd.oMapLayer.end() )
      {
          const int i = static_cast<int>(dd.oMapLayer.size()) + 1;
          dd.oMapLayer[hLayer] = LayerDescription(i);
          oGlobalMapLayer[hLayer] =
              OGRAPISpyGetDSVar(hDS) + "_" + CPLSPrintf("lyr%d", i);
      }
  
      return OGRAPISpyGetDSVar(hDS) + "_" +
             CPLSPrintf("lyr%d", hLayer ? dd.oMapLayer[hLayer].iLayer : 0);
  }
  
  static CPLString OGRAPISpyGetSRS( OGRSpatialReferenceH hSpatialRef )
  {
      if( hSpatialRef == nullptr )
          return "None";
  
      char* pszWKT = nullptr;
      OGRSpatialReference::FromHandle(hSpatialRef)->exportToWkt(&pszWKT);
      const char* pszRet =
          CPLSPrintf("osr.SpatialReference(\"\"\"%s\"\"\")", pszWKT);
      CPLFree(pszWKT);
      return pszRet;
  }
  
  static CPLString OGRAPISpyGetGeom( OGRGeometryH hGeom )
  {
      if( hGeom == nullptr )
          return "None";
  
      char* pszWKT = nullptr;
      OGRGeometry::FromHandle(hGeom)->exportToWkt(&pszWKT);
      const char* pszRet = CPLSPrintf("ogr.CreateGeometryFromWkt('%s')", pszWKT);
      CPLFree(pszWKT);
      return pszRet;
  }
  
  #define casePrefixOgrDot(x)  case x: return "ogr." #x;
  
  static CPLString OGRAPISpyGetGeomType( OGRwkbGeometryType eType )
  {
      switch( eType )
      {
          casePrefixOgrDot(wkbUnknown)
          casePrefixOgrDot(wkbPoint)
          casePrefixOgrDot(wkbLineString)
          casePrefixOgrDot(wkbPolygon)
          casePrefixOgrDot(wkbMultiPoint)
          casePrefixOgrDot(wkbMultiLineString)
          casePrefixOgrDot(wkbMultiPolygon)
          casePrefixOgrDot(wkbGeometryCollection)
          casePrefixOgrDot(wkbCircularString)
          casePrefixOgrDot(wkbCompoundCurve)
          casePrefixOgrDot(wkbCurvePolygon)
          casePrefixOgrDot(wkbMultiCurve)
          casePrefixOgrDot(wkbMultiSurface)
          casePrefixOgrDot(wkbCurve)
          casePrefixOgrDot(wkbSurface)
          casePrefixOgrDot(wkbNone)
          casePrefixOgrDot(wkbLinearRing)
          casePrefixOgrDot(wkbCircularStringZ)
          casePrefixOgrDot(wkbCompoundCurveZ)
          casePrefixOgrDot(wkbCurvePolygonZ)
          casePrefixOgrDot(wkbMultiCurveZ)
          casePrefixOgrDot(wkbMultiSurfaceZ)
          casePrefixOgrDot(wkbCurveZ)
          casePrefixOgrDot(wkbSurfaceZ)
          casePrefixOgrDot(wkbPoint25D)
          casePrefixOgrDot(wkbLineString25D)
          casePrefixOgrDot(wkbPolygon25D)
          casePrefixOgrDot(wkbMultiPoint25D)
          casePrefixOgrDot(wkbMultiLineString25D)
          casePrefixOgrDot(wkbMultiPolygon25D)
          casePrefixOgrDot(wkbGeometryCollection25D)
          casePrefixOgrDot(wkbPolyhedralSurface)
          casePrefixOgrDot(wkbTIN)
          casePrefixOgrDot(wkbTriangle)
          casePrefixOgrDot(wkbPolyhedralSurfaceZ)
          casePrefixOgrDot(wkbTINZ)
          casePrefixOgrDot(wkbTriangleZ)
          casePrefixOgrDot(wkbPointM)
          casePrefixOgrDot(wkbLineStringM)
          casePrefixOgrDot(wkbPolygonM)
          casePrefixOgrDot(wkbMultiPointM)
          casePrefixOgrDot(wkbMultiLineStringM)
          casePrefixOgrDot(wkbMultiPolygonM)
          casePrefixOgrDot(wkbGeometryCollectionM)
          casePrefixOgrDot(wkbCircularStringM)
          casePrefixOgrDot(wkbCompoundCurveM)
          casePrefixOgrDot(wkbCurvePolygonM)
          casePrefixOgrDot(wkbMultiCurveM)
          casePrefixOgrDot(wkbMultiSurfaceM)
          casePrefixOgrDot(wkbCurveM)
          casePrefixOgrDot(wkbSurfaceM)
          casePrefixOgrDot(wkbPolyhedralSurfaceM)
          casePrefixOgrDot(wkbTINM)
          casePrefixOgrDot(wkbTriangleM)
          casePrefixOgrDot(wkbPointZM)
          casePrefixOgrDot(wkbLineStringZM)
          casePrefixOgrDot(wkbPolygonZM)
          casePrefixOgrDot(wkbMultiPointZM)
          casePrefixOgrDot(wkbMultiLineStringZM)
          casePrefixOgrDot(wkbMultiPolygonZM)
          casePrefixOgrDot(wkbGeometryCollectionZM)
          casePrefixOgrDot(wkbCircularStringZM)
          casePrefixOgrDot(wkbCompoundCurveZM)
          casePrefixOgrDot(wkbCurvePolygonZM)
          casePrefixOgrDot(wkbMultiCurveZM)
          casePrefixOgrDot(wkbMultiSurfaceZM)
          casePrefixOgrDot(wkbCurveZM)
          casePrefixOgrDot(wkbSurfaceZM)
          casePrefixOgrDot(wkbPolyhedralSurfaceZM)
          casePrefixOgrDot(wkbTriangleZM)
          casePrefixOgrDot(wkbTINZM)
  
      }
      return "error";
  }
  
  static CPLString OGRAPISpyGetFieldType(OGRFieldType eType)
  {
      switch( eType )
      {
          casePrefixOgrDot(OFTInteger)
          casePrefixOgrDot(OFTInteger64)
          casePrefixOgrDot(OFTIntegerList)
          casePrefixOgrDot(OFTInteger64List)
          casePrefixOgrDot(OFTReal)
          casePrefixOgrDot(OFTRealList)
          casePrefixOgrDot(OFTString)
          casePrefixOgrDot(OFTStringList)
          casePrefixOgrDot(OFTWideString)
          casePrefixOgrDot(OFTWideStringList)
          casePrefixOgrDot(OFTBinary)
          casePrefixOgrDot(OFTDate)
          casePrefixOgrDot(OFTTime)
          casePrefixOgrDot(OFTDateTime)
      }
      return "error";
  }
  
  #undef casePrefixOgrDot
  
  static CPLString OGRAPISpyGetFeatureDefnVar( OGRFeatureDefnH hFDefn )
  {
      std::map<OGRFeatureDefnH, FeatureDefnDescription>::iterator oIter =
          oMapFDefn.find(hFDefn);
      int i = 0;
      if( oIter == oMapFDefn.end() )
      {
          i = static_cast<int>(oMapFDefn.size()) + 1;
          oMapFDefn[hFDefn] = FeatureDefnDescription(hFDefn, i);
  
          // So that we can check when they are no longer used.
          OGRFeatureDefn::FromHandle(hFDefn)->Reference();
      }
      else
      {
          i = oIter->second.iUniqueNumber;
      }
      return CPLSPrintf("fdefn%d", i);
  }
  
  static void OGRAPISpyFlushDefered()
  {
      OGRAPISpyFileReopen();
      if( hLayerGetLayerDefn != nullptr )
      {
          OGRFeatureDefnH hDefn =
              OGRFeatureDefn::ToHandle(
                  OGRLayer::FromHandle(hLayerGetLayerDefn)->
                      GetLayerDefn());
          fprintf(fpSpyFile, "%s = %s.GetLayerDefn()\n",
              OGRAPISpyGetFeatureDefnVar(hDefn).c_str(),
              OGRAPISpyGetLayerVar(hLayerGetLayerDefn).c_str());
  
          if( bDeferGetFieldCount )
          {
              fprintf(fpSpyFile, "%s.GetFieldCount()\n",
                      OGRAPISpyGetFeatureDefnVar(hDefn).c_str());
              bDeferGetFieldCount = false;
          }
  
          hLayerGetLayerDefn = nullptr;
      }
  
      if( nGetNextFeatureCalls == 1)
      {
          fprintf(fpSpyFile, "%s.GetNextFeature()\n",
              OGRAPISpyGetLayerVar(hLayerGetNextFeature).c_str());
          hLayerGetNextFeature = nullptr;
          nGetNextFeatureCalls = 0;
      }
      else if( nGetNextFeatureCalls > 0)
      {
          fprintf(fpSpyFile, "for i in range(%d):\n", nGetNextFeatureCalls);
          fprintf(fpSpyFile, "    %s.GetNextFeature()\n",
              OGRAPISpyGetLayerVar(hLayerGetNextFeature).c_str());
          hLayerGetNextFeature = nullptr;
          nGetNextFeatureCalls = 0;
      }
  }
  
  int OGRAPISpyOpenTakeSnapshot( const char* pszName, int bUpdate )
  {
      if( !OGRAPISpyEnabled() || !bUpdate || osSnapshotPath.empty() ||
          aoSetCreatedDS.find(pszName) != aoSetCreatedDS.end() )
          return -1;
      OGRAPISpyFlushDefered();
  
      VSIStatBufL sStat;
      if( VSIStatL( pszName, &sStat ) == 0 )
      {
          GDALDatasetH hDS =
              GDALOpenEx(pszName, GDAL_OF_VECTOR, nullptr, nullptr, nullptr);
          if( hDS )
          {
              char** papszFileList = GDALDataset::FromHandle(hDS)->GetFileList();
              GDALClose(hDS);
              if( papszFileList )
              {
                  int i = 1;
                  CPLString osBaseDir;
                  CPLString osSrcDir;
                  CPLString osWorkingDir;
                  while( true )
                  {
                      osBaseDir =
                          CPLFormFilename(osSnapshotPath,
                                          CPLSPrintf("snapshot_%d", i), nullptr );
                      if( VSIStatL( osBaseDir, &sStat ) != 0 )
                          break;
                      i++;
                  }
                  VSIMkdir( osBaseDir, 0777 );
                  osSrcDir = CPLFormFilename( osBaseDir, "source", nullptr );
                  VSIMkdir( osSrcDir, 0777 );
                  osWorkingDir = CPLFormFilename( osBaseDir, "working", nullptr );
                  VSIMkdir( osWorkingDir, 0777 );
  
                  OGRAPISpyFileReopen();
                  fprintf(fpSpyFile, "# Take snapshot of %s\n", pszName);
                  fprintf(fpSpyFile, "try:\n");
                  fprintf(fpSpyFile, "    shutil.rmtree('%s')\n",
                          osWorkingDir.c_str());
                  fprintf(fpSpyFile, "except:\n");
                  fprintf(fpSpyFile, "    pass\n");
                  fprintf(fpSpyFile, "os.mkdir('%s')\n", osWorkingDir.c_str());
                  for( char** papszIter = papszFileList; *papszIter; papszIter++ )
                  {
                      CPLString osSnapshotSrcFile = CPLFormFilename(
                              osSrcDir, CPLGetFilename(*papszIter), nullptr);
                      CPLString osSnapshotWorkingFile = CPLFormFilename(
                              osWorkingDir, CPLGetFilename(*papszIter), nullptr);
                      CPLCopyFile( osSnapshotSrcFile, *papszIter );
                      CPLCopyFile( osSnapshotWorkingFile, *papszIter );
                      fprintf(fpSpyFile, "shutil.copy('%s', '%s')\n",
                              osSnapshotSrcFile.c_str(),
                              osSnapshotWorkingFile.c_str());
                  }
                  CSLDestroy(papszFileList);
                  return i;
              }
          }
      }
      return -1;
  }
  
  void OGRAPISpyOpen( const char* pszName, int bUpdate, int iSnapshot,
                      GDALDatasetH* phDS )
  {
      if( !OGRAPISpyEnabled() ) return;
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
  
      CPLString osName;
      if( iSnapshot > 0 )
      {
          CPLString osBaseDir =
              CPLFormFilename(osSnapshotPath,
                              CPLSPrintf("snapshot_%d", iSnapshot), nullptr );
          CPLString osWorkingDir = CPLFormFilename( osBaseDir, "working", nullptr );
          osName = CPLFormFilename(osWorkingDir, CPLGetFilename(pszName), nullptr);
          pszName = osName.c_str();
  
          if( *phDS != nullptr )
          {
              GDALClose( GDALDataset::FromHandle(*phDS) );
              *phDS = GDALOpenEx(pszName,
                                 GDAL_OF_VECTOR | GDAL_OF_UPDATE,
                                 nullptr, nullptr, nullptr);
          }
      }
  
      OGRAPISpyFileReopen();
      if( *phDS != nullptr )
          fprintf(fpSpyFile,
                  "%s = ",
                  OGRAPISpyGetDSVar(
                      reinterpret_cast<OGRDataSourceH>(*phDS)).c_str());
      fprintf(fpSpyFile, "ogr.Open(%s, update=%d)\n",
              OGRAPISpyGetString(pszName).c_str(), bUpdate);
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpyPreClose( OGRDataSourceH hDS )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "ds%d = None\n", oMapDS[hDS].iDS);
      oSetDSIndex.erase(oMapDS[hDS].iDS);
      oMapDS.erase(hDS);
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpyPostClose()
  {
      if( !GDALIsInGlobalDestructor() )
      {
          CPLMutexHolderD(&hMutex);
          std::map<OGRFeatureDefnH, FeatureDefnDescription>::iterator oIter =
              oMapFDefn.begin();
          std::vector<OGRFeatureDefnH> oArray;
          for( ; oIter != oMapFDefn.end(); ++oIter )
          {
              FeatureDefnDescription& featureDefnDescription = oIter->second;
              if( OGRFeatureDefn::FromHandle(featureDefnDescription.hFDefn)->
                      GetReferenceCount() == 1 )
              {
                  oArray.push_back(featureDefnDescription.hFDefn);
              }
          }
          for( size_t i = 0; i < oArray.size(); i++ )
          {
              FeatureDefnDescription& featureDefnDescription =
                  oMapFDefn[oArray[i]];
              OGRFeatureDefn::FromHandle(featureDefnDescription.hFDefn)->Release();
              featureDefnDescription.Free();
              oMapFDefn.erase(oArray[i]);
          }
      }
  }
  
  void OGRAPISpyCreateDataSource( OGRSFDriverH hDriver, const char* pszName,
                                  char** papszOptions, OGRDataSourceH hDS )
  {
      if( !OGRAPISpyEnabled() ) return;
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      if( hDS != nullptr )
          fprintf(fpSpyFile, "%s = ", OGRAPISpyGetDSVar(hDS).c_str());
      fprintf(fpSpyFile,
              "ogr.GetDriverByName('%s').CreateDataSource(%s, options=%s)\n",
              GDALGetDriverShortName(reinterpret_cast<GDALDriverH>(hDriver)),
              OGRAPISpyGetString(pszName).c_str(),
              OGRAPISpyGetOptions(papszOptions).c_str());
      if( hDS != nullptr )
      {
          aoSetCreatedDS.insert(pszName);
      }
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpyDeleteDataSource( OGRSFDriverH hDriver, const char* pszName )
  {
      if( !OGRAPISpyEnabled() ) return;
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "ogr.GetDriverByName('%s').DeleteDataSource(%s)\n",
              GDALGetDriverShortName(reinterpret_cast<GDALDriverH>(hDriver)),
              OGRAPISpyGetString(pszName).c_str());
      aoSetCreatedDS.erase(pszName);
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_DS_GetLayer( OGRDataSourceH hDS, int iLayer, OGRLayerH hLayer )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      if( hLayer != nullptr )
          fprintf(fpSpyFile, "%s = ",
              OGRAPISpyGetAndRegisterLayerVar(hDS, hLayer).c_str());
      fprintf(fpSpyFile, "%s.GetLayer(%d)\n",
              OGRAPISpyGetDSVar(hDS).c_str(),
              iLayer);
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_DS_GetLayerCount( OGRDataSourceH hDS )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.GetLayerCount()\n", OGRAPISpyGetDSVar(hDS).c_str());
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_DS_GetLayerByName( OGRDataSourceH hDS, const char* pszLayerName,
                                    OGRLayerH hLayer )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      if( hLayer != nullptr )
          fprintf(fpSpyFile, "%s = ",
              OGRAPISpyGetAndRegisterLayerVar(hDS, hLayer).c_str());
      fprintf(fpSpyFile, "%s.GetLayerByName(%s)\n",
              OGRAPISpyGetDSVar(hDS).c_str(),
              OGRAPISpyGetString(pszLayerName).c_str());
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_DS_ExecuteSQL( OGRDataSourceH hDS,
                                const char *pszStatement,
                                OGRGeometryH hSpatialFilter,
                                const char *pszDialect,
                                OGRLayerH hLayer )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      if( hLayer != nullptr )
          fprintf(fpSpyFile, "%s = ",
              OGRAPISpyGetAndRegisterLayerVar(hDS, hLayer).c_str());
      fprintf(fpSpyFile, "%s.ExecuteSQL(%s, %s, %s)\n",
              OGRAPISpyGetDSVar(hDS).c_str(),
              OGRAPISpyGetString(pszStatement).c_str(),
              OGRAPISpyGetGeom(hSpatialFilter).c_str(),
              OGRAPISpyGetString(pszDialect).c_str());
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_DS_ReleaseResultSet( OGRDataSourceH hDS, OGRLayerH hLayer )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.ReleaseResultSet(%s)\n",
              OGRAPISpyGetDSVar(hDS).c_str(),
              (hLayer) ? OGRAPISpyGetLayerVar(hLayer).c_str() : "None");
  
      DatasetDescription& dd = oMapDS[hDS];
      dd.oMapLayer.erase(hLayer);
      oGlobalMapLayer.erase(hLayer);
  
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_DS_CreateLayer( OGRDataSourceH hDS,
                                 const char * pszName,
                                 OGRSpatialReferenceH hSpatialRef,
                                 OGRwkbGeometryType eType,
                                 char ** papszOptions,
                                 OGRLayerH hLayer )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      if( hLayer != nullptr )
          fprintf(fpSpyFile, "%s = ",
              OGRAPISpyGetAndRegisterLayerVar(hDS, hLayer).c_str());
      fprintf(fpSpyFile,
              "%s.CreateLayer(%s, srs=%s, geom_type=%s, options=%s)\n",
              OGRAPISpyGetDSVar(hDS).c_str(),
              OGRAPISpyGetString(pszName).c_str(),
              OGRAPISpyGetSRS(hSpatialRef).c_str(),
              OGRAPISpyGetGeomType(eType).c_str(),
              OGRAPISpyGetOptions(papszOptions).c_str());
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_DS_DeleteLayer( OGRDataSourceH hDS, int iLayer )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.DeleteLayer(%d)\n",
              OGRAPISpyGetDSVar(hDS).c_str(), iLayer);
      // Should perhaps remove from the maps.
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_Dataset_StartTransaction( GDALDatasetH hDS, int bForce )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.StartTransaction(%d)\n",
              OGRAPISpyGetDSVar(reinterpret_cast<OGRDataSourceH>(hDS)).c_str(),
                                bForce);
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_Dataset_CommitTransaction( GDALDatasetH hDS )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.CommitTransaction()\n",
              OGRAPISpyGetDSVar(reinterpret_cast<OGRDataSourceH>(hDS)).c_str());
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_Dataset_RollbackTransaction( GDALDatasetH hDS )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.RollbackTransaction()\n",
              OGRAPISpyGetDSVar(reinterpret_cast<OGRDataSourceH>(hDS)).c_str());
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_L_GetFeatureCount( OGRLayerH hLayer, int bForce )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.GetFeatureCount(force=%d)\n",
              OGRAPISpyGetLayerVar(hLayer).c_str(), bForce);
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_L_GetExtent( OGRLayerH hLayer, int bForce )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.GetExtent(force=%d)\n",
              OGRAPISpyGetLayerVar(hLayer).c_str(), bForce);
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_L_GetExtentEx( OGRLayerH hLayer, int iGeomField, int bForce )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.GetExtent(geom_field=%d, force=%d)\n",
              OGRAPISpyGetLayerVar(hLayer).c_str(), iGeomField, bForce);
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_L_SetAttributeFilter( OGRLayerH hLayer, const char* pszFilter )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.SetAttributeFilter(%s)\n",
              OGRAPISpyGetLayerVar(hLayer).c_str(),
              OGRAPISpyGetString(pszFilter).c_str());
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_L_GetFeature( OGRLayerH hLayer, GIntBig nFeatureId )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.GetFeature(" CPL_FRMT_GIB ")\n",
              OGRAPISpyGetLayerVar(hLayer).c_str(), nFeatureId);
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_L_SetNextByIndex( OGRLayerH hLayer, GIntBig nIndex )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.SetNextByIndex(" CPL_FRMT_GIB ")\n",
              OGRAPISpyGetLayerVar(hLayer).c_str(), nIndex);
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_L_GetNextFeature( OGRLayerH hLayer )
  {
      CPLMutexHolderD(&hMutex);
      if( hLayerGetNextFeature != hLayer )
      {
          OGRAPISpyFlushDefered();
          OGRAPISpyFileClose();
      }
      hLayerGetNextFeature = hLayer;
      nGetNextFeatureCalls++;
  }
  
  static void OGRAPISpyDumpFeature( OGRFeatureH hFeat )
  {
      OGRFeature* poFeature = OGRFeature::FromHandle(hFeat);
  
      fprintf(fpSpyFile, "f = ogr.Feature(%s)\n",
              OGRAPISpyGetFeatureDefnVar(OGRFeatureDefn::ToHandle(poFeature->
                     GetDefnRef())).c_str());
      if( poFeature->GetFID() != -1 )
          fprintf(fpSpyFile, "f.SetFID(" CPL_FRMT_GIB ")\n", poFeature->GetFID());
      for( int i = 0; i < poFeature->GetFieldCount(); i++ )
      {
          if( poFeature->IsFieldNull(i) )
          {
              fprintf(fpSpyFile, "f.SetFieldNull(%d)\n", i);
          }
          else if( poFeature->IsFieldSet(i) )
          {
              switch( poFeature->GetFieldDefnRef(i)->GetType())
              {
                  case OFTInteger:
                      fprintf(fpSpyFile, "f.SetField(%d, %d)\n", i,
                      poFeature->GetFieldAsInteger(i));
                      break;
                  case OFTReal:
                      fprintf(fpSpyFile,
                              "%s", CPLSPrintf("f.SetField(%d, %.16g)\n", i,
                      poFeature->GetFieldAsDouble(i)));
                      break;
                  case OFTString:
                      fprintf(fpSpyFile, "f.SetField(%d, %s)\n", i,
                      OGRAPISpyGetString(poFeature->GetFieldAsString(i)).c_str());
                      break;
                  default:
                      fprintf(fpSpyFile, "f.SetField(%d, %s) #FIXME\n", i,
                      OGRAPISpyGetString(poFeature->GetFieldAsString(i)).c_str());
                      break;
              }
          }
      }
      for( int i = 0; i < poFeature->GetGeomFieldCount(); i++ )
      {
          OGRGeometry* poGeom = poFeature->GetGeomFieldRef(i);
          if( poGeom != nullptr )
          {
              fprintf(fpSpyFile, "f.SetGeomField(%d, %s)\n",
                      i,
                      OGRAPISpyGetGeom(OGRGeometry::ToHandle(poGeom)).c_str());
          }
      }
      const char* pszStyleString = poFeature->GetStyleString();
      if( pszStyleString != nullptr )
          fprintf(fpSpyFile, "f.SetStyleString(%s)\n",
                  OGRAPISpyGetString(pszStyleString).c_str() );
  }
  
  void OGRAPISpy_L_SetFeature( OGRLayerH hLayer, OGRFeatureH hFeat )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      OGRAPISpyDumpFeature(hFeat);
      fprintf(fpSpyFile, "%s.SetFeature(f)\n",
              OGRAPISpyGetLayerVar(hLayer).c_str());
      // In case layer defn is changed afterwards.
      fprintf(fpSpyFile, "f = None\n");
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_L_CreateFeature( OGRLayerH hLayer, OGRFeatureH hFeat )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      OGRAPISpyDumpFeature(hFeat);
      fprintf(fpSpyFile, "%s.CreateFeature(f)\n",
              OGRAPISpyGetLayerVar(hLayer).c_str());
      // In case layer defn is changed afterwards.
      fprintf(fpSpyFile, "f = None\n");
      OGRAPISpyFileClose();
  }
  
  static void OGRAPISpyDumpFieldDefn( OGRFieldDefn* poFieldDefn )
  {
      CPLMutexHolderD(&hMutex);
      fprintf(fpSpyFile, "fd = ogr.FieldDefn(%s, %s)\n",
              OGRAPISpyGetString(poFieldDefn->GetNameRef()).c_str(),
              OGRAPISpyGetFieldType(poFieldDefn->GetType()).c_str());
      if( poFieldDefn->GetWidth() > 0 )
          fprintf(fpSpyFile, "fd.SetWidth(%d)\n", poFieldDefn->GetWidth() );
      if( poFieldDefn->GetPrecision() > 0 )
          fprintf(fpSpyFile, "fd.SetPrecision(%d)\n",
                  poFieldDefn->GetPrecision() );
      if( !poFieldDefn->IsNullable() )
          fprintf(fpSpyFile, "fd.SetNullable(0)\n");
      if( poFieldDefn->GetDefault() != nullptr )
          fprintf(fpSpyFile, "fd.SetDefault(%s)\n",
                  OGRAPISpyGetString(poFieldDefn->GetDefault()).c_str());
  }
  
  void OGRAPISpy_L_CreateField( OGRLayerH hLayer, OGRFieldDefnH hField,
                                int bApproxOK )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      OGRFieldDefn* poFieldDefn = OGRFieldDefn::FromHandle(hField);
      OGRAPISpyDumpFieldDefn(poFieldDefn);
      fprintf(fpSpyFile, "%s.CreateField(fd, approx_ok=%d)\n",
              OGRAPISpyGetLayerVar(hLayer).c_str(), bApproxOK);
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_L_DeleteField( OGRLayerH hLayer, int iField )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.DeleteField(%d)\n",
              OGRAPISpyGetLayerVar(hLayer).c_str(), iField);
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_L_ReorderFields( OGRLayerH hLayer, int* panMap )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      OGRLayer* poLayer = OGRLayer::FromHandle(hLayer);
      fprintf(fpSpyFile, "%s.ReorderFields([",
              OGRAPISpyGetLayerVar(hLayer).c_str());
      for( int i = 0; i < poLayer->GetLayerDefn()->GetFieldCount(); i++ )
      {
          if( i > 0 ) fprintf(fpSpyFile, ", ");
          fprintf(fpSpyFile, "%d", panMap[i]);
      }
      fprintf(fpSpyFile, "])\n");
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_L_ReorderField( OGRLayerH hLayer, int iOldFieldPos,
                                 int iNewFieldPos )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.ReorderField(%d, %d)\n",
              OGRAPISpyGetLayerVar(hLayer).c_str(), iOldFieldPos, iNewFieldPos);
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_L_AlterFieldDefn( OGRLayerH hLayer, int iField,
                                   OGRFieldDefnH hNewFieldDefn, int nFlags )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      OGRFieldDefn* poFieldDefn = OGRFieldDefn::FromHandle(hNewFieldDefn);
      OGRAPISpyDumpFieldDefn(poFieldDefn);
      fprintf(fpSpyFile, "%s.AlterFieldDefn(%d, fd, %d)\n",
              OGRAPISpyGetLayerVar(hLayer).c_str(), iField, nFlags);
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_L_CreateGeomField( OGRLayerH hLayer, OGRGeomFieldDefnH hField,
                                    int bApproxOK )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      OGRGeomFieldDefn* poGeomFieldDefn = OGRGeomFieldDefn::FromHandle(hField);
  
      fprintf(fpSpyFile, "geom_fd = ogr.GeomFieldDefn(%s, %s)\n",
              OGRAPISpyGetString(poGeomFieldDefn->GetNameRef()).c_str(),
              OGRAPISpyGetGeomType(poGeomFieldDefn->GetType()).c_str());
      if( poGeomFieldDefn->GetSpatialRef() != nullptr )
          fprintf(
              fpSpyFile, "geom_fd.SetSpatialRef(%s)\n",
              OGRAPISpyGetSRS(
                  OGRSpatialReference::ToHandle
                      (poGeomFieldDefn->GetSpatialRef())).c_str() );
      if( !poGeomFieldDefn->IsNullable() )
          fprintf(fpSpyFile, "geom_fd.SetNullable(0)\n");
      fprintf(fpSpyFile, "%s.CreateGeomField(geom_fd, approx_ok=%d)\n",
              OGRAPISpyGetLayerVar(hLayer).c_str(), bApproxOK);
      OGRAPISpyFileClose();
  }
  
  static void OGRAPISpy_L_Op( OGRLayerH hLayer, const char* pszMethod )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.%s()\n",
              OGRAPISpyGetLayerVar(hLayer).c_str(), pszMethod);
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_L_StartTransaction( OGRLayerH hLayer )
      { OGRAPISpy_L_Op(hLayer, "StartTransaction"); }
  void OGRAPISpy_L_CommitTransaction( OGRLayerH hLayer )
      { OGRAPISpy_L_Op(hLayer, "CommitTransaction"); }
  void OGRAPISpy_L_RollbackTransaction( OGRLayerH hLayer )
      { OGRAPISpy_L_Op(hLayer, "RollbackTransaction"); }
  
  void OGRAPISpy_L_GetLayerDefn( OGRLayerH hLayer )
  {
      if( hLayer != hLayerGetLayerDefn )
      {
          OGRAPISpyFlushDefered();
          hLayerGetLayerDefn = hLayer;
          OGRAPISpyFileClose();
      }
  }
  
  void OGRAPISpy_L_GetSpatialRef( OGRLayerH hLayer )
      { OGRAPISpy_L_Op(hLayer, "GetSpatialRef"); }
  void OGRAPISpy_L_GetSpatialFilter( OGRLayerH hLayer )
      { OGRAPISpy_L_Op(hLayer, "GetSpatialFilter"); }
  void OGRAPISpy_L_ResetReading( OGRLayerH hLayer )
      { OGRAPISpy_L_Op(hLayer, "ResetReading"); }
  void OGRAPISpy_L_SyncToDisk( OGRLayerH hLayer )
      { OGRAPISpy_L_Op(hLayer, "SyncToDisk"); }
  void OGRAPISpy_L_GetFIDColumn( OGRLayerH hLayer )
      { OGRAPISpy_L_Op(hLayer, "GetFIDColumn"); }
  void OGRAPISpy_L_GetGeometryColumn( OGRLayerH hLayer )
      { OGRAPISpy_L_Op(hLayer, "GetGeometryColumn"); }
  void OGRAPISpy_L_GetName( OGRLayerH hLayer )
      { OGRAPISpy_L_Op(hLayer, "GetName"); }
  void OGRAPISpy_L_GetGeomType( OGRLayerH hLayer )
      { OGRAPISpy_L_Op(hLayer, "GetGeomType"); }
  
  void OGRAPISpy_L_FindFieldIndex( OGRLayerH hLayer, const char *pszFieldName,
                                   int bExactMatch )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.FindFieldIndex(%s, %d)\n",
              OGRAPISpyGetLayerVar(hLayer).c_str(),
              OGRAPISpyGetString(pszFieldName).c_str(), bExactMatch);
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_L_TestCapability( OGRLayerH hLayer, const char* pszCap )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.TestCapability(%s)\n",
              OGRAPISpyGetLayerVar(hLayer).c_str(),
              OGRAPISpyGetString(pszCap).c_str());
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_L_SetSpatialFilter( OGRLayerH hLayer, OGRGeometryH hGeom )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.SetSpatialFilter(%s)\n",
              OGRAPISpyGetLayerVar(hLayer).c_str(),
              OGRAPISpyGetGeom(hGeom).c_str());
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_L_SetSpatialFilterEx( OGRLayerH hLayer, int iGeomField,
                                       OGRGeometryH hGeom )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.SetSpatialFilter(%d, %s)\n",
              OGRAPISpyGetLayerVar(hLayer).c_str(),
              iGeomField,
              OGRAPISpyGetGeom(hGeom).c_str());
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_L_SetSpatialFilterRect( OGRLayerH hLayer,
                                         double dfMinX, double dfMinY,
                                         double dfMaxX, double dfMaxY )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s",
              CPLSPrintf("%s.SetSpatialFilterRect(%.16g, %.16g, %.16g, %.16g)\n",
              OGRAPISpyGetLayerVar(hLayer).c_str(),
              dfMinX, dfMinY, dfMaxX, dfMaxY));
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_L_SetSpatialFilterRectEx( OGRLayerH hLayer, int iGeomField,
                                           double dfMinX, double dfMinY,
                                           double dfMaxX, double dfMaxY )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s", CPLSPrintf("%s.SetSpatialFilterRect(%d, "
              "%.16g, %.16g, %.16g, %.16g)\n",
              OGRAPISpyGetLayerVar(hLayer).c_str(),
              iGeomField,
              dfMinX, dfMinY, dfMaxX, dfMaxY));
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_L_DeleteFeature( OGRLayerH hLayer, GIntBig nFID )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.DeleteFeature(" CPL_FRMT_GIB ")\n",
              OGRAPISpyGetLayerVar(hLayer).c_str(), nFID);
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_L_SetIgnoredFields( OGRLayerH hLayer,
                                     const char** papszIgnoredFields )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.SetIgnoredFields(%s)\n",
              OGRAPISpyGetLayerVar(hLayer).c_str(),
              OGRAPISpyGetOptions(
                  const_cast<char **>(papszIgnoredFields)).c_str());
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_FD_GetGeomType( OGRFeatureDefnH hDefn )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.GetGeomType()\n",
              OGRAPISpyGetFeatureDefnVar(hDefn).c_str());
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_FD_GetFieldCount( OGRFeatureDefnH hDefn )
  {
      CPLMutexHolderD(&hMutex);
      if( hLayerGetLayerDefn != nullptr &&
          OGRFeatureDefn::ToHandle(
              OGRLayer::FromHandle(hLayerGetLayerDefn)->GetLayerDefn()) ==
          hDefn )
      {
          bDeferGetFieldCount = true;
      }
      else
      {
          OGRAPISpyFlushDefered();
          fprintf(fpSpyFile, "%s.GetFieldCount()\n",
                  OGRAPISpyGetFeatureDefnVar(hDefn).c_str());
          OGRAPISpyFileClose();
      }
  }
  
  void OGRAPISpy_FD_GetFieldDefn( OGRFeatureDefnH hDefn, int iField,
                                  OGRFieldDefnH hField )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s_fielddefn%d = %s.GetFieldDefn(%d)\n",
              OGRAPISpyGetFeatureDefnVar(hDefn).c_str(),
              iField,
              OGRAPISpyGetFeatureDefnVar(hDefn).c_str(),
              iField);
  
      std::map<OGRFieldDefnH, CPLString>::iterator oIter =
                              oGlobalMapFieldDefn.find(hField);
      if( oIter == oGlobalMapFieldDefn.end() )
      {
          oMapFDefn[hDefn].oMapFieldDefn[hField] = iField;
          oGlobalMapFieldDefn[hField] =
              CPLSPrintf("%s_fielddefn%d",
                         OGRAPISpyGetFeatureDefnVar(hDefn).c_str(),
                         iField);
      }
  
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_FD_GetFieldIndex( OGRFeatureDefnH hDefn,
                                   const char* pszFieldName )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.GetFieldIndex(%s)\n",
              OGRAPISpyGetFeatureDefnVar(hDefn).c_str(),
              OGRAPISpyGetString(pszFieldName).c_str());
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_Fld_GetXXXX( OGRFieldDefnH hField, const char* pszOp )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.%s()\n",
              oGlobalMapFieldDefn[hField].c_str(), pszOp);
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_FD_GetGeomFieldCount( OGRFeatureDefnH hDefn )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.GetGeomFieldCount()\n",
              OGRAPISpyGetFeatureDefnVar(hDefn).c_str());
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_FD_GetGeomFieldDefn( OGRFeatureDefnH hDefn, int iGeomField,
                                      OGRGeomFieldDefnH hGeomField )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s_geomfielddefn%d = %s.GetGeomFieldDefn(%d)\n",
              OGRAPISpyGetFeatureDefnVar(hDefn).c_str(),
              iGeomField,
              OGRAPISpyGetFeatureDefnVar(hDefn).c_str(),
              iGeomField);
  
      std::map<OGRGeomFieldDefnH, CPLString>::iterator oIter =
                              oGlobalMapGeomFieldDefn.find(hGeomField);
      if( oIter == oGlobalMapGeomFieldDefn.end() )
      {
          oMapFDefn[hDefn].oMapGeomFieldDefn[hGeomField] = iGeomField;
          oGlobalMapGeomFieldDefn[hGeomField] =
              CPLSPrintf("%s_geomfielddefn%d",
                         OGRAPISpyGetFeatureDefnVar(hDefn).c_str(),
                         iGeomField);
      }
  
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_FD_GetGeomFieldIndex( OGRFeatureDefnH hDefn,
                                       const char* pszFieldName )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.GetGeomFieldIndex(%s)\n",
              OGRAPISpyGetFeatureDefnVar(hDefn).c_str(),
              OGRAPISpyGetString(pszFieldName).c_str());
      OGRAPISpyFileClose();
  }
  
  void OGRAPISpy_GFld_GetXXXX( OGRGeomFieldDefnH hGeomField, const char* pszOp )
  {
      CPLMutexHolderD(&hMutex);
      OGRAPISpyFlushDefered();
      fprintf(fpSpyFile, "%s.%s()\n",
              oGlobalMapGeomFieldDefn[hGeomField].c_str(), pszOp);
      OGRAPISpyFileClose();
  }
  
  void OGRAPISPYCPLSetConfigOption(const char* pszKey, const char* pszValue )
  {
      if( STARTS_WITH(pszKey, "OGR_API_SPY_") )
          return;
      if( !OGRAPISpyEnabled() )
          return;
      OGRAPISpyFlushDefered();
      if( pszValue )
      {
          fprintf(fpSpyFile, "gdal.SetConfigOption(%s, %s)\n",
                  OGRAPISpyGetString(pszKey).c_str(),
                  OGRAPISpyGetString(pszValue).c_str());
      }
      else
      {
          fprintf(fpSpyFile, "gdal.SetConfigOption(%s, None)\n",
                  OGRAPISpyGetString(pszKey).c_str());
      }
      OGRAPISpyFileClose();
  }
  
  void OGRAPISPYCPLSetThreadLocalConfigOption(const char* pszKey, const char* pszValue )
  {
      if( !OGRAPISpyEnabled() )
          return;
      OGRAPISpyFlushDefered();
      if( pszValue )
      {
          fprintf(fpSpyFile, "gdal.SetConfigOption(%s, %s) # SetThreadLocalConfigOption actually\n",
                  OGRAPISpyGetString(pszKey).c_str(),
                  OGRAPISpyGetString(pszValue).c_str());
      }
      else
      {
          fprintf(fpSpyFile, "gdal.SetConfigOption(%s, None) # SetThreadLocalConfigOption actually\n",
                  OGRAPISpyGetString(pszKey).c_str());
      }
      OGRAPISpyFileClose();
  }
  
  #endif  // OGRAPISPY_ENABLED
  