• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MerginMaps / geodiff / 3710958510

pending completion
3710958510

push

github

GitHub
allow python logger to set to None (#192)

3047 of 4066 relevant lines covered (74.94%)

409.27 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.91
/geodiff/src/geodiffutils.cpp
1
/*
2
 GEODIFF - MIT License
3
 Copyright (C) 2019 Peter Petrik
4
*/
5

6
#include "geodiff.h"
7
#include "geodiffutils.hpp"
8
#include "changeset.h"
9
#include "geodifflogger.hpp"
10
#include "geodiffcontext.hpp"
11

12
#include <stdio.h>
13
#include <stdlib.h>
14
#include <stdarg.h>
15
#include <ctype.h>
16
#include <string.h>
17
#include <sqlite3.h>
18
#include <exception>
19
#include <fstream>
20
#include <string>
21
#include <vector>
22
#include <iostream>
23
#include <sstream>
24
#include <algorithm>
25
#include <cctype>
26
#include <gpkg.h>
27
#include <locale>
28
#include <codecvt>
29
#include <limits>
30

31
#ifdef _WIN32
32
#define UNICODE
33
#include <windows.h>
34
#include <tchar.h>
35
#include <Shlwapi.h>
36
#else
37
#include <unistd.h>
38
#include <errno.h>
39
#include <sys/stat.h>
40
#endif
41

42
GeoDiffException::GeoDiffException( const std::string &msg )
28✔
43
  : std::exception()
44
  , mMsg( msg )
28✔
45
{
46
}
28✔
47

48
const char *GeoDiffException::what() const throw()
19✔
49
{
50
  return mMsg.c_str();
19✔
51
}
52

53
// ////////////////////////////////////////////////////////////////////////
54
// ////////////////////////////////////////////////////////////////////////
55
// ////////////////////////////////////////////////////////////////////////
56

57
// ////////////////////////////////////////////////////////////////////////
58
// ////////////////////////////////////////////////////////////////////////
59
// ////////////////////////////////////////////////////////////////////////
60

61
Buffer::Buffer() = default;
746✔
62

63
Buffer::~Buffer()
746✔
64
{
65
  free();
746✔
66
}
746✔
67

68
bool Buffer::isEmpty() const
×
69
{
70
  return mAlloc == 0;
×
71
}
72

73
void Buffer::free()
1,299✔
74
{
75
  if ( mZ )
1,299✔
76
  {
77
    sqlite3_free( mZ );
636✔
78
    mZ = nullptr;
636✔
79
    mAlloc = 0;
636✔
80
    mUsed = 0;
636✔
81
  }
82
}
1,299✔
83

84
void Buffer::read( const std::string &filename )
553✔
85
{
86
  // https://stackoverflow.com/questions/3747086/reading-the-whole-text-file-into-a-char-array-in-c
87

88
  // clean the buffer
89
  free();
553✔
90

91
  /* Open the file */
92
  FILE *fp = openFile( filename, "rb" );
553✔
93
  if ( nullptr == fp )
553✔
94
  {
95
    throw GeoDiffException( "Unable to open " + filename );
2✔
96
  }
97

98
  /* Seek to the end of the file */
99
  int rc = fseek( fp, 0L, SEEK_END );
551✔
100
  if ( 0 != rc )
551✔
101
  {
102
    fclose( fp );
×
103
    throw GeoDiffException( "Unable to seek the end of " + filename );
×
104
  }
105

106
  long off_end;
107
  /* Byte offset to the end of the file (size) */
108
  if ( 0 > ( off_end = ftell( fp ) ) )
551✔
109
  {
110
    fclose( fp );
×
111
    throw GeoDiffException( "Unable to read file size of " + filename );
×
112
  }
113
  mAlloc = ( size_t )off_end;
551✔
114
  mUsed = mAlloc;
551✔
115

116
  if ( mAlloc == 0 )
551✔
117
  {
118
    // empty file
119
    fclose( fp );
108✔
120
    return;
108✔
121
  }
122

123
  /* Allocate a buffer to hold the whole file */
124
  mZ = ( char * ) sqlite3_malloc( mAlloc );
443✔
125
  if ( mZ == nullptr )
443✔
126
  {
127
    fclose( fp );
×
128
    throw GeoDiffException( "Out of memory to read " + filename + " to internal buffer" );
×
129
  }
130

131
  /* Rewind file pointer to start of file */
132
  rewind( fp );
443✔
133

134
  /* Slurp file into buffer */
135
  if ( mAlloc != ( int ) fread( mZ, 1, mAlloc, fp ) )
443✔
136
  {
137
    fclose( fp );
×
138
    throw GeoDiffException( "Unable to read " + filename + " to internal buffer" );
×
139
  }
140

141
  /* Close the file */
142
  if ( EOF == fclose( fp ) )
443✔
143
  {
144
    throw GeoDiffException( "Unable to close " + filename );
×
145
  }
146
}
147

148
void Buffer::read( int size, void *stream )
×
149
{
150
  mAlloc = size;
×
151
  mUsed = size;
×
152
  mZ = ( char * ) stream;
×
153
}
×
154

155
void Buffer::printf( const char *zFormat, ... )
386✔
156
{
157
  int nNew;
158
  for ( ;; )
159
  {
160
    if ( mZ )
386✔
161
    {
162
      va_list ap;
163
      va_start( ap, zFormat );
193✔
164
      sqlite3_vsnprintf( mAlloc - mUsed, mZ + mUsed, zFormat, ap );
193✔
165
      va_end( ap );
193✔
166
      nNew = ( int )strlen( mZ + mUsed );
193✔
167
    }
168
    else
169
    {
170
      nNew = mAlloc;
193✔
171
    }
172
    if ( mUsed + nNew < mAlloc - 1 )
386✔
173
    {
174
      mUsed += nNew;
193✔
175
      break;
193✔
176
    }
177
    mAlloc = mAlloc * 2 + 1000;
193✔
178
    mZ = ( char * ) sqlite3_realloc( mZ, mAlloc );
193✔
179
    if ( mZ == nullptr )
193✔
180
    {
181
      throw GeoDiffException( "out of memory in Buffer::printf" );
×
182
    }
183
  }
193✔
184
}
193✔
185

186
void Buffer::write( const std::string &filename )
×
187
{
188
  FILE *f = openFile( filename, "wb" );
×
189
  if ( !f )
×
190
  {
191
    throw GeoDiffException( "Unable to open " + filename + " for writing" );
×
192
  }
193
  fwrite( mZ, mAlloc, 1, f );
×
194
  fclose( f );
×
195
}
×
196

197
const char *Buffer::c_buf() const
14,183✔
198
{
199
  return mZ;
14,183✔
200
}
201

202
void *Buffer::v_buf() const
×
203
{
204
  return mZ;
×
205
}
206

207
int Buffer::size() const
16,464✔
208
{
209
  return mAlloc;
16,464✔
210
}
211

212
// ////////////////////////////////////////////////////////////////////////
213

214

215
std::string to_string_with_max_precision( double a_value )
17✔
216
{
217
  std::ostringstream out;
17✔
218
  // The limits digits10 ( = 15 for double) and max_digits10 ( = 17 for double) are a bit confusing.
219
  // originally had numeric_limits::digits10 + 1 here, but that was not enough for some numbers.
220
  // Also, we used std::fixed to avoid scientific notation, but that's loosing precision for small
221
  // numbers like 0.000123... where we waste a couple of digits for leading zeros.
222
  out.precision( std::numeric_limits<double>::max_digits10 );
17✔
223
  out << a_value;
17✔
224
  return out.str();
34✔
225
}
17✔
226

227
//
228

229
FILE *openFile( const std::string &path, const std::string &mode )
553✔
230
{
231
#ifdef WIN32
232
  // convert string path to wstring
233
  FILE *fh;
234
  errno_t err = _wfopen_s( &fh, stringToWString( path ).c_str(), stringToWString( mode ).c_str() );
235
  if ( err )
236
    return nullptr;
237
  return fh;
238
#else
239
  return fopen( path.c_str(), mode.c_str() );
553✔
240
#endif
241
}
242

243
bool filecopy( const std::string &to, const std::string &from )
54✔
244
{
245
  fileremove( to );
54✔
246

247
#ifdef WIN32
248
  try
249
  {
250
    std::wstring wFrom = stringToWString( from );
251
    std::wstring wTo = stringToWString( to );
252
    CopyFile( wFrom.c_str(), wTo.c_str(), false );
253
  }
254
  catch ( GeoDiffException & )
255
  {
256
    return false;
257
  }
258
#else
259
  std::ifstream  src( from, std::ios::binary );
54✔
260
  std::ofstream  dst( to,   std::ios::binary );
54✔
261

262
  dst << src.rdbuf();
54✔
263
#endif
264
  return true;
54✔
265
}
54✔
266

267
bool fileremove( const std::string &path )
245✔
268
{
269
  if ( fileexists( path ) )
245✔
270
  {
271
#ifdef WIN32
272
    int res = 0;
273
    try
274
    {
275
      res = _wremove( stringToWString( path ).c_str() );
276
    }
277
    catch ( GeoDiffException & )
278
    {
279
      return false;
280
    }
281
#else
282
    int res = remove( path.c_str() );
138✔
283
#endif
284
    return res == 0;
138✔
285
  }
286
  return true;  // nothing to delete, no problem...
107✔
287
}
288

289
bool fileexists( const std::string &path )
1,027✔
290
{
291
#ifdef WIN32
292
  try
293
  {
294
    std::wstring wPath = stringToWString( path );
295
    if ( wPath.empty() )
296
      return false;
297

298
    return PathFileExists( wPath.c_str() );
299
  }
300
  catch ( GeoDiffException & )
301
  {
302
    return false;
303
  }
304
#else
305
  // https://stackoverflow.com/a/12774387/2838364
306
  struct stat buffer;
307
  return ( stat( path.c_str(), &buffer ) == 0 );
1,027✔
308
#endif
309
}
310

311
bool startsWith( const std::string &str, const std::string &substr )
21,225✔
312
{
313
  if ( str.size() < substr.size() )
21,225✔
314
    return false;
3,007✔
315

316
  return str.rfind( substr, 0 ) == 0;
18,218✔
317
}
318

319
std::string replace( const std::string &str, const std::string &substr, const std::string &replacestr )
2,918✔
320
{
321
  std::string res( str );
2,918✔
322

323
  size_t i = 0;
2,918✔
324
  while ( res.find( substr,  i ) != std::string::npos )
2,930✔
325
  {
326
    i = res.find( substr, i );
12✔
327
    res.replace( i, substr.size(), replacestr );
12✔
328
    i = i + replacestr.size();
12✔
329
  }
330
  return res;
2,918✔
331
}
×
332

333

334
bool isLayerTable( const std::string &tableName )
138✔
335
{
336
  /* typically geopackage from ogr would have these (table name is simple)
337
  gpkg_contents
338
  gpkg_extensions
339
  gpkg_geometry_columns
340
  gpkg_ogr_contents
341
  gpkg_spatial_ref_sys
342
  gpkg_tile_matrix
343
  gpkg_tile_matrix_set
344
  rtree_simple_geometry_node
345
  rtree_simple_geometry_parent
346
  rtree_simple_geometry_rowid
347
  simple (or any other name(s) of layers)
348
  sqlite_sequence
349
  */
350

351
  // table handled by triggers trigger_*_feature_count_*
352
  if ( startsWith( tableName, "gpkg_" ) )
138✔
353
    return false;
110✔
354
  // table handled by triggers rtree_*_geometry_*
355
  if ( startsWith( tableName, "rtree_" ) )
28✔
356
    return false;
×
357
  // internal table for AUTOINCREMENT
358
  if ( tableName == "sqlite_sequence" )
28✔
359
    return false;
×
360

361
  return true;
28✔
362
}
363

364

365
////
366

367
void get_primary_key( const ChangesetEntry &entry, int &fid, int &nColumn )
121✔
368
{
369
  const std::vector<bool> &tablePkeys = entry.table->primaryKeys;
121✔
370

371
  // lets assume for now it has only one PK and it is int...
372
  bool found_primary_key = false;
121✔
373
  size_t pk_column_number = 0;
121✔
374
  for ( size_t i = 0; i < tablePkeys.size(); ++i )
605✔
375
  {
376
    if ( tablePkeys[i] )
484✔
377
    {
378
      if ( found_primary_key )
121✔
379
      {
380
        // ups primary key composite!
381
        throw GeoDiffException( "internal error in _get_primary_key: support composite primary keys not implemented" );
×
382
      }
383
      pk_column_number = i;
121✔
384
      found_primary_key = true;
121✔
385
    }
386
  }
387
  if ( !found_primary_key )
121✔
388
  {
389
    throw GeoDiffException( "internal error in _get_primary_key: unable to find internal key" );
×
390
  }
391

392
  nColumn = ( int ) pk_column_number;
121✔
393

394
  // now get the value
395
  Value pkeyValue;
121✔
396
  if ( entry.op == ChangesetEntry::OpInsert )
121✔
397
  {
398
    pkeyValue = entry.newValues[pk_column_number];
79✔
399
  }
400
  else if ( entry.op == ChangesetEntry::OpDelete || entry.op == ChangesetEntry::OpUpdate )
42✔
401
  {
402
    pkeyValue = entry.oldValues[pk_column_number];
42✔
403
  }
404
  if ( pkeyValue.type() == Value::TypeUndefined || pkeyValue.type() == Value::TypeNull )
121✔
405
    throw GeoDiffException( "internal error in _get_primary_key: unable to get value of primary key" );
×
406

407
  if ( pkeyValue.type() == Value::TypeInt )
121✔
408
  {
409
    int64_t val = pkeyValue.getInt();
121✔
410
    fid = ( int ) val;
121✔
411
    return;
121✔
412
  }
413
  else if ( pkeyValue.type() == Value::TypeText )
×
414
  {
415
    std::string str = pkeyValue.getString();
×
416
    const char *strData = str.data();
×
417
    int hash = 0;
×
418
    size_t len = str.size();
×
419
    for ( size_t i = 0; i < len; i++ )
×
420
    {
421
      hash = 33 * hash + ( unsigned char )strData[i];
×
422
    }
423
    fid = hash;
×
424
  }
×
425
  else
426
  {
427
    throw GeoDiffException( "internal error in _get_primary_key: unsuported type of primary key" );
×
428
  }
429
}
121✔
430

431

432
bool flushString( const std::string &filename, const std::string &str )
52✔
433
{
434
#ifdef WIN32
435
  try
436
  {
437
    std::wstring wFilename = stringToWString( filename );
438
    std::ofstream out( wFilename );
439
    out << str;
440
    out.close();
441
  }
442
  catch ( GeoDiffException & )
443
  {
444
    return false;
445
  }
446
#else
447
  std::ofstream out( filename );
52✔
448
  out << str;
52✔
449
  out.close();
52✔
450
#endif
451
  return true;
52✔
452
}
52✔
453

454
std::string getEnvVar( std::string const &key, const std::string &defaultVal )
503✔
455
{
456
  std::string ret = defaultVal;
503✔
457

458
#ifdef WIN32
459
  char *val = nullptr;
460
  size_t sz = 0;
461
  if ( _dupenv_s( &val, &sz, key.c_str() ) == 0 && val != nullptr )
462
  {
463
    ret = std::string( val );
464
    free( val );
465
  }
466
#else
467
  char *val = getenv( key.c_str() );
503✔
468
  if ( val )
503✔
469
    ret = std::string( val );
28✔
470
#endif
471

472
  return ret;
503✔
473
}
×
474

475
int getEnvVarInt( std::string const &key, int defaultVal )
29✔
476
{
477
  std::string envVal = getEnvVar( key, "" );
58✔
478
  if ( envVal.empty() )
29✔
479
    return defaultVal;
29✔
480
  else
481
    return atoi( envVal.c_str() );
×
482
}
29✔
483

484
std::string tmpdir()
446✔
485
{
486
#ifdef WIN32
487
  wchar_t arr[MAX_PATH];
488
  DWORD dwRetVal = GetTempPathW( MAX_PATH, arr );
489

490
  std::wstring tempDirPath( arr );
491
  if ( dwRetVal > MAX_PATH || ( dwRetVal == 0 ) )
492
  {
493
    return std::string( "C:/temp/" );
494
  }
495

496
  try
497
  {
498
    return wstringToString( tempDirPath );
499
  }
500
  catch ( GeoDiffException & )
501
  {
502
    return std::string();
503
  }
504

505
#else
506
  return getEnvVar( "TMPDIR", "/tmp/" );
446✔
507
#endif
508
}
509

510
std::wstring stringToWString( const std::string &str )
×
511
{
512
  // we need to convert UTF-8 string to UTF-16 in order to use WindowsAPI
513
  // https://stackoverflow.com/questions/2573834/c-convert-string-or-char-to-wstring-or-wchar-t
514
  try
515
  {
516
    std::wstring_convert< std::codecvt_utf8_utf16< wchar_t > > converter;
×
517
    std::wstring wStr = converter.from_bytes( str );
×
518

519
    return wStr;
×
520
  }
×
521
  catch ( const std::range_error & )
×
522
  {
523
    throw GeoDiffException( "Unable to convert UTF-8 to UTF-16." );
×
524
  }
×
525
}
526

527
std::string wstringToString( const std::wstring &wStr )
×
528
{
529
  // we need to convert UTF-16 string to UTF-8 in order to use WindowsAPI
530
  // https://stackoverflow.com/questions/4804298/how-to-convert-wstring-into-string
531
  try
532
  {
533
    std::wstring_convert< std::codecvt_utf8_utf16< wchar_t > > converter;
×
534
    std::string str = converter.to_bytes( wStr );
×
535

536
    return str;
×
537
  }
×
538
  catch ( const std::range_error & )
×
539
  {
540
    throw GeoDiffException( "Unable to convert UTF-8 to UTF-16." );
×
541
  }
×
542
}
543

544
TmpFile::TmpFile( ) = default;
8✔
545

546

547
TmpFile::TmpFile( const std::string &path )
119✔
548
  : mPath( path )
119✔
549
{
550
}
119✔
551

552
TmpFile::~TmpFile()
127✔
553
{
554
  if ( fileexists( mPath ) )
127✔
555
  {
556
    fileremove( mPath );
124✔
557
  }
558
}
127✔
559

560
std::string TmpFile::path() const
53✔
561
{
562
  return mPath;
53✔
563
}
564

565
const char *TmpFile::c_path() const
233✔
566
{
567
  return mPath.c_str();
233✔
568
}
569

570
void TmpFile::setPath( const std::string &path )
5✔
571
{
572
  mPath = path;
5✔
573
}
5✔
574

575
ConflictFeature::ConflictFeature( int pk,
8✔
576
                                  const std::string &tableName )
8✔
577
  : mPk( pk )
8✔
578
  , mTableName( tableName )
8✔
579
{
580
}
8✔
581

582
bool ConflictFeature::isValid() const
8✔
583
{
584
  return !mItems.empty();
8✔
585
}
586

587
void ConflictFeature::addItem( const ConflictItem &item )
5✔
588
{
589
  mItems.push_back( item );
5✔
590
}
5✔
591

592
std::string ConflictFeature::tableName() const
9✔
593
{
594
  return mTableName;
9✔
595
}
596

597
int ConflictFeature::pk() const
4✔
598
{
599
  return mPk;
4✔
600
}
601

602
std::vector<ConflictItem> ConflictFeature::items() const
4✔
603
{
604
  return mItems;
4✔
605
}
606

607
ConflictItem::ConflictItem( int column, const Value &base,
5✔
608
                            const Value &theirs, const Value &ours )
5✔
609
  : mColumn( column )
5✔
610
  , mBase( base )
5✔
611
  , mTheirs( theirs )
5✔
612
  , mOurs( ours )
5✔
613
{
614

615
}
5✔
616

617
Value ConflictItem::base() const
5✔
618
{
619
  return mBase;
5✔
620
}
621

622
Value ConflictItem::theirs() const
5✔
623
{
624
  return mTheirs;
5✔
625
}
626

627
Value ConflictItem::ours() const
5✔
628
{
629
  return mOurs;
5✔
630
}
631

632
int ConflictItem::column() const
5✔
633
{
634
  return mColumn;
5✔
635
}
636

637
int indexOf( const std::vector<std::string> &arr, const std::string &val )
8✔
638
{
639
  std::vector<std::string>::const_iterator result = std::find( arr.begin(), arr.end(), val );
8✔
640
  if ( result == arr.end() )
8✔
641
    return -1;
×
642
  else
643
    return ( int ) std::distance( arr.begin(), result );
8✔
644
}
645

646
std::string concatNames( const std::vector<std::string> &names )
6✔
647
{
648
  std::string output;
6✔
649
  for ( const std::string &name : names )
16✔
650
  {
651
    if ( !output.empty() )
10✔
652
      output += ", ";
4✔
653
    output += name;
10✔
654
  }
655
  return output;
6✔
656
}
×
657

658
std::string lowercaseString( const std::string &str )
3,098✔
659
{
660
  std::string ret = str;
3,098✔
661
#ifdef WIN32
662
  std::transform( ret.begin(), ret.end(), ret.begin(), []( unsigned char i ) { return ( unsigned char ) std::tolower( i ); } );
663
#else
664
  std::transform( ret.begin(), ret.end(), ret.begin(), []( unsigned char i ) { return std::tolower( i ); } );
23,989✔
665
#endif
666
  return ret;
3,098✔
667
}
668

669

670
// from https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c
671
std::string randomString( size_t length )
27✔
672
{
673
  auto randchar = []() -> char
162✔
674
  {
675
    const char charset[] =
162✔
676
    "0123456789"
677
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
678
    "abcdefghijklmnopqrstuvwxyz";
679
    const size_t max_index = ( sizeof( charset ) - 1 );
162✔
680
    return charset[ rand() % max_index ];
162✔
681
  };
682
  std::string str( length, 0 );
27✔
683
  std::generate_n( str.begin(), length, randchar );
27✔
684
  return str;
54✔
685
}
686

687
std::string randomTmpFilename()
×
688
{
689
  std::string tdir = tmpdir();
×
690
  if ( tdir.empty() )
×
691
    return tdir;
×
692
  else
693
    return tmpdir() + "geodiff_" + randomString( 6 );
×
694
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc