1 /******************************************************************************
2 *
3 * Project: OpenGIS Simple Features Reference Implementation
4 * Purpose: Implements FileGDB OGR driver.
5 * Author: Ragi Yaser Burhum, ragi@burhum.com
6 * Paul Ramsey, pramsey at cleverelephant.ca
7 *
8 ******************************************************************************
9 * Copyright (c) 2010, Ragi Yaser Burhum
10 * Copyright (c) 2011, Paul Ramsey <pramsey at cleverelephant.ca>
11 * Copyright (c) 2011-2013, Even Rouault <even dot rouault at spatialys.com>
12 *
13 * Permission is hereby granted, free of charge, to any person obtaining a
14 * copy of this software and associated documentation files (the "Software"),
15 * to deal in the Software without restriction, including without limitation
16 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
17 * and/or sell copies of the Software, and to permit persons to whom the
18 * Software is furnished to do so, subject to the following conditions:
19 *
20 * The above copyright notice and this permission notice shall be included
21 * in all copies or substantial portions of the Software.
22 *
23 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
24 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
26 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29 * DEALINGS IN THE SOFTWARE.
30 ****************************************************************************/
31
32 #include "ogr_fgdb.h"
33 #include "cpl_conv.h"
34 #include "FGdbUtils.h"
35 #include "cpl_multiproc.h"
36 #include "ogrmutexeddatasource.h"
37
38 CPL_CVSID("$Id: FGdbDriver.cpp 1761acd90777d5bcc49eddbc13c193098f0ed40b 2020-10-01 12:12:00 +0200 Even Rouault $")
39
40 extern "C" void RegisterOGRFileGDB();
41
42 /************************************************************************/
43 /* FGdbDriver() */
44 /************************************************************************/
FGdbDriver()45 FGdbDriver::FGdbDriver(): OGRSFDriver(), hMutex(nullptr)
46 {
47 }
48
49 /************************************************************************/
50 /* ~FGdbDriver() */
51 /************************************************************************/
~FGdbDriver()52 FGdbDriver::~FGdbDriver()
53
54 {
55 if( !oMapConnections.empty() )
56 CPLDebug("FileGDB", "Remaining %d connections. Bug?",
57 (int)oMapConnections.size());
58 if( hMutex != nullptr )
59 CPLDestroyMutex(hMutex);
60 hMutex = nullptr;
61 }
62
63 /************************************************************************/
64 /* GetName() */
65 /************************************************************************/
66
GetName()67 const char *FGdbDriver::GetName()
68
69 {
70 return "FileGDB";
71 }
72
73 /************************************************************************/
74 /* Open() */
75 /************************************************************************/
76
Open(const char * pszFilename,int bUpdate)77 OGRDataSource *FGdbDriver::Open( const char* pszFilename, int bUpdate )
78
79 {
80 // First check if we have to do any work.
81 size_t nLen = strlen(pszFilename);
82 if( nLen == 1 && pszFilename[0] == '.' )
83 {
84 char* pszCurrentDir = CPLGetCurrentDir();
85 if( pszCurrentDir )
86 {
87 size_t nLen2 = strlen(pszCurrentDir);
88 bool bOK = (nLen2 >= 4 && EQUAL(pszCurrentDir + nLen2 - 4, ".gdb"));
89 CPLFree(pszCurrentDir);
90 if( !bOK )
91 return nullptr;
92 }
93 else
94 {
95 return nullptr;
96 }
97 }
98 else if(! ((nLen >= 4 && EQUAL(pszFilename + nLen - 4, ".gdb")) ||
99 (nLen >= 5 && EQUAL(pszFilename + nLen - 5, ".gdb/"))) )
100 return nullptr;
101
102 long hr;
103
104 /* Check that the filename is really a directory, to avoid confusion with */
105 /* Garmin MapSource - gdb format which can be a problem when the FileGDB */
106 /* driver is loaded as a plugin, and loaded before the GPSBabel driver */
107 /* (http://trac.osgeo.org/osgeo4w/ticket/245) */
108 VSIStatBuf stat;
109 if( CPLStat( pszFilename, &stat ) != 0 || !VSI_ISDIR(stat.st_mode) )
110 {
111 return nullptr;
112 }
113
114 CPLMutexHolderD(&hMutex);
115
116 FGdbDatabaseConnection* pConnection = oMapConnections[pszFilename];
117 if( pConnection != nullptr )
118 {
119 if( pConnection->IsFIDHackInProgress() )
120 {
121 CPLError(CE_Failure, CPLE_AppDefined,
122 "Cannot open geodatabase at the moment since it is in 'FID hack mode'");
123 return nullptr;
124 }
125
126 pConnection->m_nRefCount ++;
127 CPLDebug("FileGDB", "ref_count of %s = %d now", pszFilename,
128 pConnection->m_nRefCount);
129 }
130 else
131 {
132 Geodatabase* pGeoDatabase = new Geodatabase;
133 hr = ::OpenGeodatabase(StringToWString(pszFilename), *pGeoDatabase);
134
135 if (FAILED(hr))
136 {
137 delete pGeoDatabase;
138
139 if( OGRGetDriverByName("OpenFileGDB") != nullptr && bUpdate == FALSE )
140 {
141 std::wstring fgdb_error_desc_w;
142 std::string fgdb_error_desc("Unknown error");
143 fgdbError er;
144 er = FileGDBAPI::ErrorInfo::GetErrorDescription(static_cast<fgdbError>(hr), fgdb_error_desc_w);
145 if ( er == S_OK )
146 {
147 fgdb_error_desc = WStringToString(fgdb_error_desc_w);
148 }
149 CPLDebug("FileGDB", "Cannot open %s with FileGDB driver: %s. Failing silently so OpenFileGDB can be tried",
150 pszFilename,
151 fgdb_error_desc.c_str());
152 }
153 else
154 {
155 GDBErr(hr, "Failed to open Geodatabase");
156 }
157 oMapConnections.erase(pszFilename);
158 return nullptr;
159 }
160
161 CPLDebug("FileGDB", "Really opening %s", pszFilename);
162 pConnection = new FGdbDatabaseConnection(pszFilename, pGeoDatabase);
163 oMapConnections[pszFilename] = pConnection;
164 }
165
166 FGdbDataSource* pDS;
167
168 pDS = new FGdbDataSource(this, pConnection);
169
170 if(!pDS->Open( pszFilename, bUpdate, nullptr ) )
171 {
172 delete pDS;
173 return nullptr;
174 }
175 else
176 {
177 OGRMutexedDataSource* poMutexedDS =
178 new OGRMutexedDataSource(pDS, TRUE, hMutex, TRUE);
179 if( bUpdate )
180 return OGRCreateEmulatedTransactionDataSourceWrapper(poMutexedDS, this, TRUE, FALSE);
181 else
182 return poMutexedDS;
183 }
184 }
185
186 /***********************************************************************/
187 /* CreateDataSource() */
188 /***********************************************************************/
189
CreateDataSource(const char * conn,char ** papszOptions)190 OGRDataSource* FGdbDriver::CreateDataSource( const char * conn,
191 char **papszOptions)
192 {
193 long hr;
194 Geodatabase *pGeodatabase;
195 std::wstring wconn = StringToWString(conn);
196 int bUpdate = TRUE; // If we're creating, we must be writing.
197 VSIStatBuf stat;
198
199 CPLMutexHolderD(&hMutex);
200
201 /* We don't support options yet, so warn if they send us some */
202 if ( papszOptions )
203 {
204 /* TODO: warning, ignoring options */
205 }
206
207 /* Only accept names of form "filename.gdb" and */
208 /* also .gdb.zip to be able to return FGDB with MapServer OGR output (#4199) */
209 const char* pszExt = CPLGetExtension(conn);
210 if ( !(EQUAL(pszExt,"gdb") || EQUAL(pszExt, "zip")) )
211 {
212 CPLError( CE_Failure, CPLE_AppDefined,
213 "FGDB data source name must use 'gdb' extension.\n" );
214 return nullptr;
215 }
216
217 /* Don't try to create on top of something already there */
218 if( CPLStat( conn, &stat ) == 0 )
219 {
220 CPLError( CE_Failure, CPLE_AppDefined,
221 "%s already exists.\n", conn );
222 return nullptr;
223 }
224
225 /* Try to create the geodatabase */
226 pGeodatabase = new Geodatabase; // Create on heap so we can store it in the Datasource
227 hr = CreateGeodatabase(wconn, *pGeodatabase);
228
229 /* Handle creation errors */
230 if ( S_OK != hr )
231 {
232 const char *errstr = "Error creating geodatabase (%s).\n";
233 if ( hr == -2147220653 )
234 errstr = "File already exists (%s).\n";
235 delete pGeodatabase;
236 CPLError( CE_Failure, CPLE_AppDefined, errstr, conn );
237 return nullptr;
238 }
239
240 FGdbDatabaseConnection* pConnection = new FGdbDatabaseConnection(conn, pGeodatabase);
241 oMapConnections[conn] = pConnection;
242
243 /* Ready to embed the Geodatabase in an OGR Datasource */
244 FGdbDataSource* pDS = new FGdbDataSource(this, pConnection);
245 if ( ! pDS->Open(conn, bUpdate, nullptr) )
246 {
247 delete pDS;
248 return nullptr;
249 }
250 else
251 return OGRCreateEmulatedTransactionDataSourceWrapper(
252 new OGRMutexedDataSource(pDS, TRUE, hMutex, TRUE), this,
253 TRUE, FALSE);
254 }
255
256 /************************************************************************/
257 /* StartTransaction() */
258 /************************************************************************/
259
StartTransaction(OGRDataSource * & poDSInOut,int & bOutHasReopenedDS)260 OGRErr FGdbDriver::StartTransaction(OGRDataSource*& poDSInOut, int& bOutHasReopenedDS)
261 {
262 CPLMutexHolderOptionalLockD(hMutex);
263
264 bOutHasReopenedDS = FALSE;
265
266 OGRMutexedDataSource* poMutexedDS = (OGRMutexedDataSource*)poDSInOut;
267 FGdbDataSource* poDS = (FGdbDataSource* )poMutexedDS->GetBaseDataSource();
268 if( !poDS->GetUpdate() )
269 return OGRERR_FAILURE;
270 FGdbDatabaseConnection* pConnection = poDS->GetConnection();
271 if( pConnection->GetRefCount() != 1 )
272 {
273 CPLError(CE_Failure, CPLE_AppDefined,
274 "Cannot start transaction as database is opened in another connection");
275 return OGRERR_FAILURE;
276 }
277 if( pConnection->IsLocked() )
278 {
279 CPLError(CE_Failure, CPLE_AppDefined,
280 "Transaction is already in progress");
281 return OGRERR_FAILURE;
282 }
283
284 bOutHasReopenedDS = TRUE;
285
286 CPLString osName(poMutexedDS->GetName());
287 CPLString osNameOri(osName);
288 if( osName.back()== '/' || osName.back()== '\\' )
289 osName.resize(osName.size()-1);
290
291 #ifndef WIN32
292 int bPerLayerCopyingForTransaction = poDS->HasPerLayerCopyingForTransaction();
293 #endif
294
295 pConnection->m_nRefCount ++;
296 delete poDSInOut;
297 poDSInOut = nullptr;
298 poMutexedDS = nullptr;
299 poDS = nullptr;
300
301 pConnection->CloseGeodatabase();
302
303 CPLString osEditedName(osName);
304 osEditedName += ".ogredited";
305
306 CPLPushErrorHandler(CPLQuietErrorHandler);
307 CPL_IGNORE_RET_VAL(CPLUnlinkTree(osEditedName));
308 CPLPopErrorHandler();
309
310 OGRErr eErr = OGRERR_NONE;
311
312 CPLString osDatabaseToReopen;
313 #ifndef WIN32
314 if( bPerLayerCopyingForTransaction )
315 {
316 int bError = FALSE;
317
318 if( VSIMkdir( osEditedName, 0755 ) != 0 )
319 {
320 CPLError( CE_Failure, CPLE_AppDefined,
321 "Cannot create directory '%s'.",
322 osEditedName.c_str() );
323 bError = TRUE;
324 }
325
326 // Only copy a0000000X.Y files with X >= 1 && X <= 8, gdb and timestamps
327 // and symlink others
328 char** papszFiles = VSIReadDir(osName);
329 for(char** papszIter = papszFiles; !bError && *papszIter; ++papszIter)
330 {
331 if( strcmp(*papszIter, ".") == 0 || strcmp(*papszIter, "..") == 0 )
332 continue;
333 if( ((*papszIter)[0] == 'a' && atoi((*papszIter)+1) >= 1 &&
334 atoi((*papszIter)+1) <= 8) || EQUAL(*papszIter, "gdb") ||
335 EQUAL(*papszIter, "timestamps") )
336 {
337 if( CPLCopyFile(CPLFormFilename(osEditedName, *papszIter, nullptr),
338 CPLFormFilename(osName, *papszIter, nullptr)) != 0 )
339 {
340 bError = TRUE;
341 CPLError(CE_Failure, CPLE_AppDefined,
342 "Cannot copy %s", *papszIter);
343 }
344 }
345 else
346 {
347 CPLString osSourceFile;
348 if( CPLIsFilenameRelative(osName) )
349 osSourceFile = CPLFormFilename(CPLSPrintf("../%s", CPLGetFilename(osName.c_str())), *papszIter, nullptr);
350 else
351 osSourceFile = osName;
352 if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") ||
353 CPLSymlink( osSourceFile,
354 CPLFormFilename(osEditedName.c_str(), *papszIter, nullptr),
355 nullptr ) != 0 )
356 {
357 bError = TRUE;
358 CPLError(CE_Failure, CPLE_AppDefined,
359 "Cannot symlink %s", *papszIter);
360 }
361 }
362 }
363 CSLDestroy(papszFiles);
364
365 if( bError )
366 {
367 eErr = OGRERR_FAILURE;
368 osDatabaseToReopen = osName;
369 }
370 else
371 osDatabaseToReopen = osEditedName;
372 }
373 else
374 #endif
375 {
376 if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") ||
377 CPLCopyTree( osEditedName, osName ) != 0 )
378 {
379 CPLError(CE_Failure, CPLE_AppDefined,
380 "Cannot backup geodatabase");
381 eErr = OGRERR_FAILURE;
382 osDatabaseToReopen = osName;
383 }
384 else
385 osDatabaseToReopen = osEditedName;
386 }
387
388 pConnection->m_pGeodatabase = new Geodatabase;
389 long hr = ::OpenGeodatabase(StringToWString(osDatabaseToReopen), *(pConnection->m_pGeodatabase));
390 if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE2") || FAILED(hr))
391 {
392 delete pConnection->m_pGeodatabase;
393 pConnection->m_pGeodatabase = nullptr;
394 Release(osName);
395 GDBErr(hr, CPLSPrintf("Failed to open %s. Dataset should be closed",
396 osDatabaseToReopen.c_str()));
397
398 return OGRERR_FAILURE;
399 }
400
401 FGdbDataSource* pDS = new FGdbDataSource(this, pConnection);
402 pDS->Open(osDatabaseToReopen, TRUE, osNameOri);
403
404 #ifndef WIN32
405 if( eErr == OGRERR_NONE && bPerLayerCopyingForTransaction )
406 {
407 pDS->SetPerLayerCopyingForTransaction(bPerLayerCopyingForTransaction);
408 pDS->SetSymlinkFlagOnAllLayers();
409 }
410 #endif
411
412 poDSInOut = new OGRMutexedDataSource(pDS, TRUE, hMutex, TRUE);
413
414 if( eErr == OGRERR_NONE )
415 pConnection->SetLocked(TRUE);
416 return eErr;
417 }
418
419 /************************************************************************/
420 /* CommitTransaction() */
421 /************************************************************************/
422
CommitTransaction(OGRDataSource * & poDSInOut,int & bOutHasReopenedDS)423 OGRErr FGdbDriver::CommitTransaction(OGRDataSource*& poDSInOut, int& bOutHasReopenedDS)
424 {
425 CPLMutexHolderOptionalLockD(hMutex);
426
427 bOutHasReopenedDS = FALSE;
428
429 OGRMutexedDataSource* poMutexedDS = (OGRMutexedDataSource*)poDSInOut;
430 FGdbDataSource* poDS = (FGdbDataSource* )poMutexedDS->GetBaseDataSource();
431 FGdbDatabaseConnection* pConnection = poDS->GetConnection();
432 if( !pConnection->IsLocked() )
433 {
434 CPLError(CE_Failure, CPLE_NotSupported,
435 "No transaction in progress");
436 return OGRERR_FAILURE;
437 }
438
439 bOutHasReopenedDS = TRUE;
440
441 CPLString osName(poMutexedDS->GetName());
442 CPLString osNameOri(osName);
443 if( osName.back()== '/' || osName.back()== '\\' )
444 osName.resize(osName.size()-1);
445
446 #ifndef WIN32
447 int bPerLayerCopyingForTransaction = poDS->HasPerLayerCopyingForTransaction();
448 #endif
449
450 pConnection->m_nRefCount ++;
451 delete poDSInOut;
452 poDSInOut = nullptr;
453 poMutexedDS = nullptr;
454 poDS = nullptr;
455
456 pConnection->CloseGeodatabase();
457
458 CPLString osEditedName(osName);
459 osEditedName += ".ogredited";
460
461 #ifndef WIN32
462 if( bPerLayerCopyingForTransaction )
463 {
464 int bError = FALSE;
465 char** papszFiles;
466 std::vector<CPLString> aosTmpFilesToClean;
467
468 // Check for files present in original copy that are not in edited copy
469 // That is to say deleted layers
470 papszFiles = VSIReadDir(osName);
471 for(char** papszIter = papszFiles; !bError && *papszIter; ++papszIter)
472 {
473 if( strcmp(*papszIter, ".") == 0 || strcmp(*papszIter, "..") == 0 )
474 continue;
475 VSIStatBufL sStat;
476 if( (*papszIter)[0] == 'a' &&
477 VSIStatL( CPLFormFilename(osEditedName, *papszIter, nullptr), &sStat ) != 0 )
478 {
479 if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") ||
480 VSIRename( CPLFormFilename(osName, *papszIter, nullptr),
481 CPLFormFilename(osName, *papszIter, "tmp") ) != 0 )
482 {
483 CPLError(CE_Failure, CPLE_AppDefined, "Cannot rename %s to %s",
484 CPLFormFilename(osName, *papszIter, nullptr),
485 CPLFormFilename(osName, *papszIter, "tmp"));
486 bError = TRUE;
487 }
488 else
489 aosTmpFilesToClean.push_back(CPLFormFilename(osName, *papszIter, "tmp"));
490 }
491 }
492 CSLDestroy(papszFiles);
493
494 // Move modified files from edited directory to main directory
495 papszFiles = VSIReadDir(osEditedName);
496 for(char** papszIter = papszFiles; !bError && *papszIter; ++papszIter)
497 {
498 if( strcmp(*papszIter, ".") == 0 || strcmp(*papszIter, "..") == 0 )
499 continue;
500 struct stat sStat;
501 if( lstat( CPLFormFilename(osEditedName, *papszIter, nullptr), &sStat ) != 0 )
502 {
503 CPLError(CE_Failure, CPLE_AppDefined, "Cannot stat %s",
504 CPLFormFilename(osEditedName, *papszIter, nullptr));
505 bError = TRUE;
506 }
507 else if( !S_ISLNK(sStat.st_mode) )
508 {
509 // If there was such a file in original directory, first rename it
510 // as a temporary file
511 if( lstat( CPLFormFilename(osName, *papszIter, nullptr), &sStat ) == 0 )
512 {
513 if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE2") ||
514 VSIRename( CPLFormFilename(osName, *papszIter, nullptr),
515 CPLFormFilename(osName, *papszIter, "tmp") ) != 0 )
516 {
517 CPLError(CE_Failure, CPLE_AppDefined, "Cannot rename %s to %s",
518 CPLFormFilename(osName, *papszIter, nullptr),
519 CPLFormFilename(osName, *papszIter, "tmp"));
520 bError = TRUE;
521 }
522 else
523 aosTmpFilesToClean.push_back(CPLFormFilename(osName, *papszIter, "tmp"));
524 }
525 if( !bError )
526 {
527 if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE3") ||
528 CPLMoveFile( CPLFormFilename(osName, *papszIter, nullptr),
529 CPLFormFilename(osEditedName, *papszIter, nullptr) ) != 0 )
530 {
531 CPLError(CE_Failure, CPLE_AppDefined, "Cannot move %s to %s",
532 CPLFormFilename(osEditedName, *papszIter, nullptr),
533 CPLFormFilename(osName, *papszIter, nullptr));
534 bError = TRUE;
535 }
536 else
537 CPLDebug("FileGDB", "Move %s to %s",
538 CPLFormFilename(osEditedName, *papszIter, nullptr),
539 CPLFormFilename(osName, *papszIter, nullptr));
540 }
541 }
542 }
543 CSLDestroy(papszFiles);
544
545 if( !bError )
546 {
547 for(size_t i=0;i<aosTmpFilesToClean.size();i++)
548 {
549 if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE4") ||
550 VSIUnlink(aosTmpFilesToClean[i]) != 0 )
551 {
552 CPLError(CE_Warning, CPLE_AppDefined,
553 "Cannot remove %s. Manual cleanup required", aosTmpFilesToClean[i].c_str());
554 }
555 }
556 }
557
558 if( bError )
559 {
560 CPLError(CE_Failure, CPLE_AppDefined,
561 "An error occurred while moving files from %s back to %s. "
562 "Manual cleaning must be done and dataset should be closed",
563 osEditedName.c_str(),
564 osName.c_str());
565 pConnection->SetLocked(FALSE);
566 Release(osName);
567 return OGRERR_FAILURE;
568 }
569 else if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE5") ||
570 CPLUnlinkTree(osEditedName) != 0 )
571 {
572 CPLError(CE_Warning, CPLE_AppDefined,
573 "Cannot remove %s. Manual cleanup required", osEditedName.c_str());
574 }
575 }
576 else
577 #endif
578 {
579 CPLString osTmpName(osName);
580 osTmpName += ".ogrtmp";
581
582 /* Install the backup copy as the main database in 3 steps : */
583 /* first rename the main directory in .tmp */
584 /* then rename the edited copy under regular name */
585 /* and finally dispose the .tmp directory */
586 /* That way there's no risk definitely losing data */
587 if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") ||
588 VSIRename(osName, osTmpName) != 0 )
589 {
590 CPLError(CE_Failure, CPLE_AppDefined,
591 "Cannot rename %s to %s. Edited database during transaction is in %s"
592 "Dataset should be closed",
593 osName.c_str(), osTmpName.c_str(), osEditedName.c_str());
594 pConnection->SetLocked(FALSE);
595 Release(osName);
596 return OGRERR_FAILURE;
597 }
598
599 if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE2") ||
600 VSIRename(osEditedName, osName) != 0 )
601 {
602 CPLError(CE_Failure, CPLE_AppDefined,
603 "Cannot rename %s to %s. The original geodatabase is in '%s'. "
604 "Dataset should be closed",
605 osEditedName.c_str(), osName.c_str(), osTmpName.c_str());
606 pConnection->SetLocked(FALSE);
607 Release(osName);
608 return OGRERR_FAILURE;
609 }
610
611 if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE3") ||
612 CPLUnlinkTree(osTmpName) != 0 )
613 {
614 CPLError(CE_Warning, CPLE_AppDefined,
615 "Cannot remove %s. Manual cleanup required", osTmpName.c_str());
616 }
617 }
618
619 pConnection->m_pGeodatabase = new Geodatabase;
620 long hr = ::OpenGeodatabase(StringToWString(osName), *(pConnection->m_pGeodatabase));
621 if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE_REOPEN") || FAILED(hr))
622 {
623 delete pConnection->m_pGeodatabase;
624 pConnection->m_pGeodatabase = nullptr;
625 pConnection->SetLocked(FALSE);
626 Release(osName);
627 GDBErr(hr, "Failed to re-open Geodatabase. Dataset should be closed");
628 return OGRERR_FAILURE;
629 }
630
631 FGdbDataSource* pDS = new FGdbDataSource(this, pConnection);
632 pDS->Open(osNameOri, TRUE, nullptr);
633 //pDS->SetPerLayerCopyingForTransaction(bPerLayerCopyingForTransaction);
634 poDSInOut = new OGRMutexedDataSource(pDS, TRUE, hMutex, TRUE);
635
636 pConnection->SetLocked(FALSE);
637
638 return OGRERR_NONE;
639 }
640
641 /************************************************************************/
642 /* RollbackTransaction() */
643 /************************************************************************/
644
RollbackTransaction(OGRDataSource * & poDSInOut,int & bOutHasReopenedDS)645 OGRErr FGdbDriver::RollbackTransaction(OGRDataSource*& poDSInOut, int& bOutHasReopenedDS)
646 {
647 CPLMutexHolderOptionalLockD(hMutex);
648
649 bOutHasReopenedDS = FALSE;
650
651 OGRMutexedDataSource* poMutexedDS = (OGRMutexedDataSource*)poDSInOut;
652 FGdbDataSource* poDS = (FGdbDataSource* )poMutexedDS->GetBaseDataSource();
653 FGdbDatabaseConnection* pConnection = poDS->GetConnection();
654 if( !pConnection->IsLocked() )
655 {
656 CPLError(CE_Failure, CPLE_NotSupported,
657 "No transaction in progress");
658 return OGRERR_FAILURE;
659 }
660
661 bOutHasReopenedDS = TRUE;
662
663 CPLString osName(poMutexedDS->GetName());
664 CPLString osNameOri(osName);
665 if( osName.back()== '/' || osName.back()== '\\' )
666 osName.resize(osName.size()-1);
667
668 //int bPerLayerCopyingForTransaction = poDS->HasPerLayerCopyingForTransaction();
669
670 pConnection->m_nRefCount ++;
671 delete poDSInOut;
672 poDSInOut = nullptr;
673 poMutexedDS = nullptr;
674 poDS = nullptr;
675
676 pConnection->CloseGeodatabase();
677
678 CPLString osEditedName(osName);
679 osEditedName += ".ogredited";
680
681 OGRErr eErr = OGRERR_NONE;
682 if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") ||
683 CPLUnlinkTree(osEditedName) != 0 )
684 {
685 CPLError(CE_Warning, CPLE_AppDefined,
686 "Cannot remove %s. Manual cleanup required", osEditedName.c_str());
687 eErr = OGRERR_FAILURE;
688 }
689
690 pConnection->m_pGeodatabase = new Geodatabase;
691 long hr = ::OpenGeodatabase(StringToWString(osName), *(pConnection->m_pGeodatabase));
692 if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE2") ||
693 FAILED(hr))
694 {
695 delete pConnection->m_pGeodatabase;
696 pConnection->m_pGeodatabase = nullptr;
697 pConnection->SetLocked(FALSE);
698 Release(osName);
699 GDBErr(hr, "Failed to re-open Geodatabase. Dataset should be closed");
700 return OGRERR_FAILURE;
701 }
702
703 FGdbDataSource* pDS = new FGdbDataSource(this, pConnection);
704 pDS->Open(osNameOri, TRUE, nullptr);
705 //pDS->SetPerLayerCopyingForTransaction(bPerLayerCopyingForTransaction);
706 poDSInOut = new OGRMutexedDataSource(pDS, TRUE, hMutex, TRUE);
707
708 pConnection->SetLocked(FALSE);
709
710 return eErr;
711 }
712
713 /***********************************************************************/
714 /* Release() */
715 /***********************************************************************/
716
Release(const char * pszName)717 void FGdbDriver::Release(const char* pszName)
718 {
719 CPLMutexHolderOptionalLockD(hMutex);
720
721 FGdbDatabaseConnection* pConnection = oMapConnections[pszName];
722 if( pConnection != nullptr )
723 {
724 pConnection->m_nRefCount --;
725 CPLDebug("FileGDB", "ref_count of %s = %d now", pszName,
726 pConnection->m_nRefCount);
727 if( pConnection->m_nRefCount == 0 )
728 {
729 pConnection->CloseGeodatabase();
730 delete pConnection;
731 oMapConnections.erase(pszName);
732 }
733 }
734 }
735
736 /***********************************************************************/
737 /* CloseGeodatabase() */
738 /***********************************************************************/
739
CloseGeodatabase()740 void FGdbDatabaseConnection::CloseGeodatabase()
741 {
742 if( m_pGeodatabase != nullptr )
743 {
744 CPLDebug("FileGDB", "Really closing %s now", m_osName.c_str());
745 ::CloseGeodatabase(*m_pGeodatabase);
746 delete m_pGeodatabase;
747 m_pGeodatabase = nullptr;
748 }
749 }
750
751 /***********************************************************************/
752 /* OpenGeodatabase() */
753 /***********************************************************************/
754
OpenGeodatabase(const char * pszFSName)755 int FGdbDatabaseConnection::OpenGeodatabase(const char* pszFSName)
756 {
757 m_pGeodatabase = new Geodatabase;
758 long hr = ::OpenGeodatabase(StringToWString(CPLString(pszFSName)), *m_pGeodatabase);
759 if (FAILED(hr))
760 {
761 delete m_pGeodatabase;
762 m_pGeodatabase = nullptr;
763 return FALSE;
764 }
765 return TRUE;
766 }
767
768 /***********************************************************************/
769 /* TestCapability() */
770 /***********************************************************************/
771
TestCapability(const char * pszCap)772 int FGdbDriver::TestCapability( const char * pszCap )
773 {
774 if (EQUAL(pszCap, ODrCCreateDataSource) )
775 return TRUE;
776
777 else if (EQUAL(pszCap, ODrCDeleteDataSource) )
778 return TRUE;
779
780 return FALSE;
781 }
782 /************************************************************************/
783 /* DeleteDataSource() */
784 /************************************************************************/
785
DeleteDataSource(const char * pszDataSource)786 OGRErr FGdbDriver::DeleteDataSource( const char *pszDataSource )
787 {
788 CPLMutexHolderD(&hMutex);
789
790 std::wstring wstr = StringToWString(pszDataSource);
791
792 long hr = 0;
793
794 if( S_OK != (hr = ::DeleteGeodatabase(wstr)) )
795 {
796 GDBErr(hr, "Failed to delete Geodatabase");
797 return OGRERR_FAILURE;
798 }
799
800 return OGRERR_NONE;
801 }
802
803 /***********************************************************************/
804 /* RegisterOGRFileGDB() */
805 /***********************************************************************/
806
RegisterOGRFileGDB()807 void RegisterOGRFileGDB()
808
809 {
810 if (! GDAL_CHECK_VERSION("OGR FGDB"))
811 return;
812
813 OGRSFDriver* poDriver = new FGdbDriver;
814 poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
815 "ESRI FileGDB" );
816 poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "gdb" );
817 poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "drivers/vector/filegdb.html" );
818
819 poDriver->SetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST,
820 "<CreationOptionList/>" );
821
822 poDriver->SetMetadataItem( GDAL_DS_LAYER_CREATIONOPTIONLIST,
823 "<LayerCreationOptionList>"
824 " <Option name='FEATURE_DATASET' type='string' description='FeatureDataset folder into to put the new layer'/>"
825 " <Option name='LAYER_ALIAS' type='string' description='Alias of layer name'/>"
826 " <Option name='GEOMETRY_NAME' type='string' description='Name of geometry column' default='SHAPE'/>"
827 " <Option name='GEOMETRY_NULLABLE' type='boolean' description='Whether the values of the geometry column can be NULL' default='YES'/>"
828 " <Option name='FID' type='string' description='Name of OID column' default='OBJECTID' deprecated_alias='OID_NAME'/>"
829 " <Option name='XYTOLERANCE' type='float' description='Snapping tolerance, used for advanced ArcGIS features like network and topology rules, on 2D coordinates, in the units of the CRS'/>"
830 " <Option name='ZTOLERANCE' type='float' description='Snapping tolerance, used for advanced ArcGIS features like network and topology rules, on Z coordinates, in the units of the CRS'/>"
831 " <Option name='XORIGIN' type='float' description='X origin of the coordinate precision grid'/>"
832 " <Option name='YORIGIN' type='float' description='Y origin of the coordinate precision grid'/>"
833 " <Option name='ZORIGIN' type='float' description='Z origin of the coordinate precision grid'/>"
834 " <Option name='XYSCALE' type='float' description='X,Y scale of the coordinate precision grid'/>"
835 " <Option name='ZSCALE' type='float' description='Z scale of the coordinate precision grid'/>"
836 " <Option name='XML_DEFINITION' type='string' description='XML definition to create the new table. The root node of such a XML definition must be a <esri:DataElement> element conformant to FileGDBAPI.xsd'/>"
837 " <Option name='CREATE_MULTIPATCH' type='boolean' description='Whether to write geometries of layers of type MultiPolygon as MultiPatch' default='NO'/>"
838 " <Option name='COLUMN_TYPES' type='string' description='A list of strings of format field_name=fgdb_filed_type (separated by comma) to force the FileGDB column type of fields to be created'/>"
839 " <Option name='CONFIGURATION_KEYWORD' type='string-select' description='Customize how data is stored. By default text in UTF-8 and data up to 1TB'>"
840 " <Value>DEFAULTS</Value>"
841 " <Value>TEXT_UTF16</Value>"
842 " <Value>MAX_FILE_SIZE_4GB</Value>"
843 " <Value>MAX_FILE_SIZE_256TB</Value>"
844 " <Value>GEOMETRY_OUTOFLINE</Value>"
845 " <Value>BLOB_OUTOFLINE</Value>"
846 " <Value>GEOMETRY_AND_BLOB_OUTOFLINE</Value>"
847 " </Option>"
848 "</LayerCreationOptionList>");
849
850 poDriver->SetMetadataItem( GDAL_DMD_CREATIONFIELDDATATYPES,
851 "Integer Real String Date DateTime Binary" );
852 poDriver->SetMetadataItem( GDAL_DMD_CREATIONFIELDDATASUBTYPES, "Int16 Float32" );
853 poDriver->SetMetadataItem( GDAL_DCAP_NOTNULL_FIELDS, "YES" );
854 poDriver->SetMetadataItem( GDAL_DCAP_DEFAULT_FIELDS, "YES" );
855 poDriver->SetMetadataItem( GDAL_DCAP_NOTNULL_GEOMFIELDS, "YES" );
856
857 OGRSFDriverRegistrar::GetRegistrar()->RegisterDriver(poDriver);
858 }
859