LCOV - code coverage report
Current view: top level - core - qgsofflineediting.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 0 972 0.0 %
Date: 2021-04-10 08:29:14 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :     offline_editing.cpp
       3                 :            : 
       4                 :            :     Offline Editing Plugin
       5                 :            :     a QGIS plugin
       6                 :            :      --------------------------------------
       7                 :            :     Date                 : 22-Jul-2010
       8                 :            :     Copyright            : (C) 2010 by Sourcepole
       9                 :            :     Email                : info at sourcepole.ch
      10                 :            :  ***************************************************************************
      11                 :            :  *                                                                         *
      12                 :            :  *   This program is free software; you can redistribute it and/or modify  *
      13                 :            :  *   it under the terms of the GNU General Public License as published by  *
      14                 :            :  *   the Free Software Foundation; either version 2 of the License, or     *
      15                 :            :  *   (at your option) any later version.                                   *
      16                 :            :  *                                                                         *
      17                 :            :  ***************************************************************************/
      18                 :            : 
      19                 :            : 
      20                 :            : #include "qgsapplication.h"
      21                 :            : #include "qgsdatasourceuri.h"
      22                 :            : #include "qgsgeometry.h"
      23                 :            : #include "qgslayertreegroup.h"
      24                 :            : #include "qgslayertreelayer.h"
      25                 :            : #include "qgsmaplayer.h"
      26                 :            : #include "qgsofflineediting.h"
      27                 :            : #include "qgsproject.h"
      28                 :            : #include "qgsvectordataprovider.h"
      29                 :            : #include "qgsvectorlayereditbuffer.h"
      30                 :            : #include "qgsvectorlayerjoinbuffer.h"
      31                 :            : #include "qgsspatialiteutils.h"
      32                 :            : #include "qgsfeatureiterator.h"
      33                 :            : #include "qgslogger.h"
      34                 :            : #include "qgsvectorlayerutils.h"
      35                 :            : #include "qgsrelationmanager.h"
      36                 :            : #include "qgsmapthemecollection.h"
      37                 :            : #include "qgslayertree.h"
      38                 :            : #include "qgsogrutils.h"
      39                 :            : #include "qgsvectorfilewriter.h"
      40                 :            : #include "qgsvectorlayer.h"
      41                 :            : #include "qgsproviderregistry.h"
      42                 :            : #include "qgsprovidermetadata.h"
      43                 :            : #include "qgsmaplayerstylemanager.h"
      44                 :            : 
      45                 :            : #include <QDir>
      46                 :            : #include <QDomDocument>
      47                 :            : #include <QDomNode>
      48                 :            : #include <QFile>
      49                 :            : #include <QMessageBox>
      50                 :            : 
      51                 :            : #include <ogr_srs_api.h>
      52                 :            : 
      53                 :            : extern "C"
      54                 :            : {
      55                 :            : #include <sqlite3.h>
      56                 :            : #include <spatialite.h>
      57                 :            : }
      58                 :            : 
      59                 :            : #define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE "isOfflineEditable"
      60                 :            : #define CUSTOM_PROPERTY_REMOTE_SOURCE "remoteSource"
      61                 :            : #define CUSTOM_PROPERTY_REMOTE_PROVIDER "remoteProvider"
      62                 :            : #define CUSTOM_SHOW_FEATURE_COUNT "showFeatureCount"
      63                 :            : #define CUSTOM_PROPERTY_ORIGINAL_LAYERID "remoteLayerId"
      64                 :            : #define CUSTOM_PROPERTY_LAYERNAME_SUFFIX "layerNameSuffix"
      65                 :            : #define PROJECT_ENTRY_SCOPE_OFFLINE "OfflineEditingPlugin"
      66                 :            : #define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH "/OfflineDbPath"
      67                 :            : 
      68                 :          0 : QgsOfflineEditing::QgsOfflineEditing()
      69                 :          0 : {
      70                 :          0 :   connect( QgsProject::instance(), &QgsProject::layerWasAdded, this, &QgsOfflineEditing::layerAdded );
      71                 :          0 : }
      72                 :            : 
      73                 :            : /**
      74                 :            :  * convert current project to offline project
      75                 :            :  * returns offline project file path
      76                 :            :  *
      77                 :            :  * Workflow:
      78                 :            :  *
      79                 :            :  * - copy layers to SpatiaLite
      80                 :            :  * - create SpatiaLite db at offlineDataPath
      81                 :            :  * - create table for each layer
      82                 :            :  * - add new SpatiaLite layer
      83                 :            :  * - copy features
      84                 :            :  * - save as offline project
      85                 :            :  * - mark offline layers
      86                 :            :  * - remove remote layers
      87                 :            :  * - mark as offline project
      88                 :            :  */
      89                 :          0 : bool QgsOfflineEditing::convertToOfflineProject( const QString &offlineDataPath, const QString &offlineDbFile, const QStringList &layerIds, bool onlySelected, ContainerType containerType, const QString &layerNameSuffix )
      90                 :            : {
      91                 :          0 :   if ( layerIds.isEmpty() )
      92                 :            :   {
      93                 :          0 :     return false;
      94                 :            :   }
      95                 :            : 
      96                 :          0 :   QString dbPath = QDir( offlineDataPath ).absoluteFilePath( offlineDbFile );
      97                 :          0 :   if ( createOfflineDb( dbPath, containerType ) )
      98                 :            :   {
      99                 :          0 :     spatialite_database_unique_ptr database;
     100                 :          0 :     int rc = database.open( dbPath );
     101                 :          0 :     if ( rc != SQLITE_OK )
     102                 :            :     {
     103                 :          0 :       showWarning( tr( "Could not open the SpatiaLite database" ) );
     104                 :          0 :     }
     105                 :            :     else
     106                 :            :     {
     107                 :            :       // create logging tables
     108                 :          0 :       createLoggingTables( database.get() );
     109                 :            : 
     110                 :          0 :       emit progressStarted();
     111                 :            : 
     112                 :          0 :       QMap<QString, QgsVectorJoinList > joinInfoBuffer;
     113                 :          0 :       QMap<QString, QgsVectorLayer *> layerIdMapping;
     114                 :            : 
     115                 :          0 :       for ( const QString &layerId : layerIds )
     116                 :            :       {
     117                 :          0 :         QgsMapLayer *layer = QgsProject::instance()->mapLayer( layerId );
     118                 :          0 :         QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
     119                 :          0 :         if ( !vl )
     120                 :          0 :           continue;
     121                 :          0 :         QgsVectorJoinList joins = vl->vectorJoins();
     122                 :            : 
     123                 :            :         // Layer names will be appended an _offline suffix
     124                 :            :         // Join fields are prefixed with the layer name and we do not want the
     125                 :            :         // field name to change so we stabilize the field name by defining a
     126                 :            :         // custom prefix with the layername without _offline suffix.
     127                 :          0 :         QgsVectorJoinList::iterator joinIt = joins.begin();
     128                 :          0 :         while ( joinIt != joins.end() )
     129                 :            :         {
     130                 :          0 :           if ( joinIt->prefix().isNull() )
     131                 :            :           {
     132                 :          0 :             QgsVectorLayer *vl = joinIt->joinLayer();
     133                 :            : 
     134                 :          0 :             if ( vl )
     135                 :          0 :               joinIt->setPrefix( vl->name() + '_' );
     136                 :          0 :           }
     137                 :          0 :           ++joinIt;
     138                 :            :         }
     139                 :          0 :         joinInfoBuffer.insert( vl->id(), joins );
     140                 :          0 :       }
     141                 :            : 
     142                 :          0 :       QgsSnappingConfig snappingConfig = QgsProject::instance()->snappingConfig();
     143                 :            : 
     144                 :            :       // copy selected vector layers to offline layer
     145                 :          0 :       for ( int i = 0; i < layerIds.count(); i++ )
     146                 :            :       {
     147                 :          0 :         emit layerProgressUpdated( i + 1, layerIds.count() );
     148                 :            : 
     149                 :          0 :         QgsMapLayer *layer = QgsProject::instance()->mapLayer( layerIds.at( i ) );
     150                 :          0 :         QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
     151                 :          0 :         if ( vl )
     152                 :            :         {
     153                 :          0 :           QString origLayerId = vl->id();
     154                 :          0 :           QgsVectorLayer *newLayer = copyVectorLayer( vl, database.get(), dbPath, onlySelected, containerType, layerNameSuffix );
     155                 :          0 :           if ( newLayer )
     156                 :            :           {
     157                 :          0 :             layerIdMapping.insert( origLayerId, newLayer );
     158                 :            :             //append individual layer setting on snapping settings
     159                 :          0 :             snappingConfig.setIndividualLayerSettings( newLayer, snappingConfig.individualLayerSettings( vl ) );
     160                 :          0 :             snappingConfig.removeLayers( QList<QgsMapLayer *>() << vl );
     161                 :            : 
     162                 :            :             // remove remote layer
     163                 :          0 :             QgsProject::instance()->removeMapLayers(
     164                 :          0 :               QStringList() << origLayerId );
     165                 :          0 :           }
     166                 :          0 :         }
     167                 :          0 :       }
     168                 :            : 
     169                 :          0 :       QgsProject::instance()->setSnappingConfig( snappingConfig );
     170                 :            : 
     171                 :            :       // restore join info on new offline layer
     172                 :          0 :       QMap<QString, QgsVectorJoinList >::ConstIterator it;
     173                 :          0 :       for ( it = joinInfoBuffer.constBegin(); it != joinInfoBuffer.constEnd(); ++it )
     174                 :            :       {
     175                 :          0 :         QgsVectorLayer *newLayer = layerIdMapping.value( it.key() );
     176                 :            : 
     177                 :          0 :         if ( newLayer )
     178                 :            :         {
     179                 :          0 :           const QList<QgsVectorLayerJoinInfo> joins = it.value();
     180                 :          0 :           for ( QgsVectorLayerJoinInfo join : joins )
     181                 :            :           {
     182                 :          0 :             QgsVectorLayer *newJoinedLayer = layerIdMapping.value( join.joinLayerId() );
     183                 :          0 :             if ( newJoinedLayer )
     184                 :            :             {
     185                 :            :               // If the layer has been offline'd, update join information
     186                 :          0 :               join.setJoinLayer( newJoinedLayer );
     187                 :          0 :             }
     188                 :          0 :             newLayer->addJoin( join );
     189                 :          0 :           }
     190                 :          0 :         }
     191                 :          0 :       }
     192                 :            : 
     193                 :          0 :       emit progressStopped();
     194                 :            : 
     195                 :            :       // save offline project
     196                 :          0 :       QString projectTitle = QgsProject::instance()->title();
     197                 :          0 :       if ( projectTitle.isEmpty() )
     198                 :            :       {
     199                 :          0 :         projectTitle = QFileInfo( QgsProject::instance()->fileName() ).fileName();
     200                 :          0 :       }
     201                 :          0 :       projectTitle += QLatin1String( " (offline)" );
     202                 :          0 :       QgsProject::instance()->setTitle( projectTitle );
     203                 :            : 
     204                 :          0 :       QgsProject::instance()->writeEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH, QgsProject::instance()->writePath( dbPath ) );
     205                 :            : 
     206                 :          0 :       return true;
     207                 :          0 :     }
     208                 :          0 :   }
     209                 :            : 
     210                 :          0 :   return false;
     211                 :          0 : }
     212                 :            : 
     213                 :          0 : bool QgsOfflineEditing::isOfflineProject() const
     214                 :            : {
     215                 :          0 :   return !QgsProject::instance()->readEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH ).isEmpty();
     216                 :          0 : }
     217                 :            : 
     218                 :          0 : void QgsOfflineEditing::synchronize()
     219                 :            : {
     220                 :            :   // open logging db
     221                 :          0 :   sqlite3_database_unique_ptr database = openLoggingDb();
     222                 :          0 :   if ( !database )
     223                 :            :   {
     224                 :          0 :     return;
     225                 :            :   }
     226                 :            : 
     227                 :          0 :   emit progressStarted();
     228                 :            : 
     229                 :          0 :   QgsSnappingConfig snappingConfig = QgsProject::instance()->snappingConfig();
     230                 :            : 
     231                 :            :   // restore and sync remote layers
     232                 :          0 :   QList<QgsMapLayer *> offlineLayers;
     233                 :          0 :   QMap<QString, QgsMapLayer *> mapLayers = QgsProject::instance()->mapLayers();
     234                 :          0 :   for ( QMap<QString, QgsMapLayer *>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
     235                 :            :   {
     236                 :          0 :     QgsMapLayer *layer = layer_it.value();
     237                 :          0 :     if ( layer->customProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, false ).toBool() )
     238                 :            :     {
     239                 :          0 :       offlineLayers << layer;
     240                 :          0 :     }
     241                 :          0 :   }
     242                 :            : 
     243                 :          0 :   QgsDebugMsgLevel( QStringLiteral( "Found %1 offline layers" ).arg( offlineLayers.count() ), 4 );
     244                 :          0 :   for ( int l = 0; l < offlineLayers.count(); l++ )
     245                 :            :   {
     246                 :          0 :     QgsMapLayer *layer = offlineLayers.at( l );
     247                 :            : 
     248                 :          0 :     emit layerProgressUpdated( l + 1, offlineLayers.count() );
     249                 :            : 
     250                 :          0 :     QString remoteSource = layer->customProperty( CUSTOM_PROPERTY_REMOTE_SOURCE, "" ).toString();
     251                 :          0 :     QString remoteProvider = layer->customProperty( CUSTOM_PROPERTY_REMOTE_PROVIDER, "" ).toString();
     252                 :          0 :     QString remoteName = layer->name();
     253                 :          0 :     QString remoteNameSuffix = layer->customProperty( CUSTOM_PROPERTY_LAYERNAME_SUFFIX, " (offline)" ).toString();
     254                 :          0 :     if ( remoteName.endsWith( remoteNameSuffix ) )
     255                 :          0 :       remoteName.chop( remoteNameSuffix.size() );
     256                 :          0 :     const QgsVectorLayer::LayerOptions options { QgsProject::instance()->transformContext() };
     257                 :          0 :     QgsVectorLayer *remoteLayer = new QgsVectorLayer( remoteSource, remoteName, remoteProvider, options );
     258                 :          0 :     if ( remoteLayer->isValid() )
     259                 :            :     {
     260                 :            :       // Rebuild WFS cache to get feature id<->GML fid mapping
     261                 :          0 :       if ( remoteLayer->providerType().contains( QLatin1String( "WFS" ), Qt::CaseInsensitive ) )
     262                 :            :       {
     263                 :          0 :         QgsFeatureIterator fit = remoteLayer->getFeatures();
     264                 :          0 :         QgsFeature f;
     265                 :          0 :         while ( fit.nextFeature( f ) )
     266                 :            :         {
     267                 :            :         }
     268                 :          0 :       }
     269                 :            :       // TODO: only add remote layer if there are log entries?
     270                 :            : 
     271                 :          0 :       QgsVectorLayer *offlineLayer = qobject_cast<QgsVectorLayer *>( layer );
     272                 :            : 
     273                 :            :       // register this layer with the central layers registry
     274                 :          0 :       QgsProject::instance()->addMapLayers( QList<QgsMapLayer *>() << remoteLayer, true );
     275                 :            : 
     276                 :            :       // copy style
     277                 :          0 :       copySymbology( offlineLayer, remoteLayer );
     278                 :          0 :       updateRelations( offlineLayer, remoteLayer );
     279                 :          0 :       updateMapThemes( offlineLayer, remoteLayer );
     280                 :          0 :       updateLayerOrder( offlineLayer, remoteLayer );
     281                 :            : 
     282                 :            :       //append individual layer setting on snapping settings
     283                 :          0 :       snappingConfig.setIndividualLayerSettings( remoteLayer, snappingConfig.individualLayerSettings( offlineLayer ) );
     284                 :          0 :       snappingConfig.removeLayers( QList<QgsMapLayer *>() << offlineLayer );
     285                 :            : 
     286                 :            :       //set QgsLayerTreeNode properties back
     287                 :          0 :       QgsLayerTreeLayer *layerTreeLayer = QgsProject::instance()->layerTreeRoot()->findLayer( offlineLayer->id() );
     288                 :          0 :       QgsLayerTreeLayer *newLayerTreeLayer = QgsProject::instance()->layerTreeRoot()->findLayer( remoteLayer->id() );
     289                 :          0 :       newLayerTreeLayer->setCustomProperty( CUSTOM_SHOW_FEATURE_COUNT, layerTreeLayer->customProperty( CUSTOM_SHOW_FEATURE_COUNT ) );
     290                 :            : 
     291                 :            :       // apply layer edit log
     292                 :          0 :       QString qgisLayerId = layer->id();
     293                 :          0 :       QString sql = QStringLiteral( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
     294                 :          0 :       int layerId = sqlQueryInt( database.get(), sql, -1 );
     295                 :          0 :       if ( layerId != -1 )
     296                 :            :       {
     297                 :          0 :         remoteLayer->startEditing();
     298                 :            : 
     299                 :            :         // TODO: only get commitNos of this layer?
     300                 :          0 :         int commitNo = getCommitNo( database.get() );
     301                 :          0 :         QgsDebugMsgLevel( QStringLiteral( "Found %1 commits" ).arg( commitNo ), 4 );
     302                 :          0 :         for ( int i = 0; i < commitNo; i++ )
     303                 :            :         {
     304                 :          0 :           QgsDebugMsgLevel( QStringLiteral( "Apply commits chronologically" ), 4 );
     305                 :            :           // apply commits chronologically
     306                 :          0 :           applyAttributesAdded( remoteLayer, database.get(), layerId, i );
     307                 :          0 :           applyAttributeValueChanges( offlineLayer, remoteLayer, database.get(), layerId, i );
     308                 :          0 :           applyGeometryChanges( remoteLayer, database.get(), layerId, i );
     309                 :          0 :         }
     310                 :            : 
     311                 :          0 :         applyFeaturesAdded( offlineLayer, remoteLayer, database.get(), layerId );
     312                 :          0 :         applyFeaturesRemoved( remoteLayer, database.get(), layerId );
     313                 :            : 
     314                 :          0 :         if ( remoteLayer->commitChanges() )
     315                 :            :         {
     316                 :            :           // update fid lookup
     317                 :          0 :           updateFidLookup( remoteLayer, database.get(), layerId );
     318                 :            : 
     319                 :            :           // clear edit log for this layer
     320                 :          0 :           sql = QStringLiteral( "DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( layerId );
     321                 :          0 :           sqlExec( database.get(), sql );
     322                 :          0 :           sql = QStringLiteral( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
     323                 :          0 :           sqlExec( database.get(), sql );
     324                 :          0 :           sql = QStringLiteral( "DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
     325                 :          0 :           sqlExec( database.get(), sql );
     326                 :          0 :           sql = QStringLiteral( "DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
     327                 :          0 :           sqlExec( database.get(), sql );
     328                 :          0 :           sql = QStringLiteral( "DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
     329                 :          0 :           sqlExec( database.get(), sql );
     330                 :          0 :         }
     331                 :            :         else
     332                 :            :         {
     333                 :          0 :           showWarning( remoteLayer->commitErrors().join( QLatin1Char( '\n' ) ) );
     334                 :            :         }
     335                 :          0 :       }
     336                 :            :       else
     337                 :            :       {
     338                 :          0 :         QgsDebugMsg( QStringLiteral( "Could not find the layer id in the edit logs!" ) );
     339                 :            :       }
     340                 :            :       // Invalidate the connection to force a reload if the project is put offline
     341                 :            :       // again with the same path
     342                 :          0 :       offlineLayer->dataProvider()->invalidateConnections( QgsDataSourceUri( offlineLayer->source() ).database() );
     343                 :            :       // remove offline layer
     344                 :          0 :       QgsProject::instance()->removeMapLayers( QStringList() << qgisLayerId );
     345                 :            : 
     346                 :            : 
     347                 :            :       // disable offline project
     348                 :          0 :       QString projectTitle = QgsProject::instance()->title();
     349                 :          0 :       projectTitle.remove( QRegExp( " \\(offline\\)$" ) );
     350                 :          0 :       QgsProject::instance()->setTitle( projectTitle );
     351                 :          0 :       QgsProject::instance()->removeEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH );
     352                 :          0 :       remoteLayer->reload(); //update with other changes
     353                 :          0 :     }
     354                 :            :     else
     355                 :            :     {
     356                 :          0 :       QgsDebugMsg( QStringLiteral( "Remote layer is not valid!" ) );
     357                 :            :     }
     358                 :          0 :   }
     359                 :            : 
     360                 :            :   // reset commitNo
     361                 :          0 :   QString sql = QStringLiteral( "UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'" );
     362                 :          0 :   sqlExec( database.get(), sql );
     363                 :            : 
     364                 :          0 :   QgsProject::instance()->setSnappingConfig( snappingConfig );
     365                 :            : 
     366                 :          0 :   emit progressStopped();
     367                 :          0 : }
     368                 :            : 
     369                 :          0 : void QgsOfflineEditing::initializeSpatialMetadata( sqlite3 *sqlite_handle )
     370                 :            : {
     371                 :            :   // attempting to perform self-initialization for a newly created DB
     372                 :          0 :   if ( !sqlite_handle )
     373                 :          0 :     return;
     374                 :            :   // checking if this DB is really empty
     375                 :          0 :   char **results = nullptr;
     376                 :            :   int rows, columns;
     377                 :          0 :   int ret = sqlite3_get_table( sqlite_handle, "select count(*) from sqlite_master", &results, &rows, &columns, nullptr );
     378                 :          0 :   if ( ret != SQLITE_OK )
     379                 :          0 :     return;
     380                 :          0 :   int count = 0;
     381                 :          0 :   if ( rows >= 1 )
     382                 :            :   {
     383                 :          0 :     for ( int i = 1; i <= rows; i++ )
     384                 :          0 :       count = atoi( results[( i * columns ) + 0] );
     385                 :          0 :   }
     386                 :            : 
     387                 :          0 :   sqlite3_free_table( results );
     388                 :            : 
     389                 :          0 :   if ( count > 0 )
     390                 :          0 :     return;
     391                 :            : 
     392                 :          0 :   bool above41 = false;
     393                 :          0 :   ret = sqlite3_get_table( sqlite_handle, "select spatialite_version()", &results, &rows, &columns, nullptr );
     394                 :          0 :   if ( ret == SQLITE_OK && rows == 1 && columns == 1 )
     395                 :            :   {
     396                 :          0 :     QString version = QString::fromUtf8( results[1] );
     397                 :            : #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
     398                 :            :     QStringList parts = version.split( ' ', QString::SkipEmptyParts );
     399                 :            : #else
     400                 :          0 :     QStringList parts = version.split( ' ', Qt::SkipEmptyParts );
     401                 :            : #endif
     402                 :          0 :     if ( !parts.empty() )
     403                 :            :     {
     404                 :            : #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
     405                 :            :       QStringList verparts = parts.at( 0 ).split( '.', QString::SkipEmptyParts );
     406                 :            : #else
     407                 :          0 :       QStringList verparts = parts.at( 0 ).split( '.', Qt::SkipEmptyParts );
     408                 :            : #endif
     409                 :          0 :       above41 = verparts.size() >= 2 && ( verparts.at( 0 ).toInt() > 4 || ( verparts.at( 0 ).toInt() == 4 && verparts.at( 1 ).toInt() >= 1 ) );
     410                 :          0 :     }
     411                 :          0 :   }
     412                 :            : 
     413                 :          0 :   sqlite3_free_table( results );
     414                 :            : 
     415                 :            :   // all right, it's empty: proceeding to initialize
     416                 :          0 :   char *errMsg = nullptr;
     417                 :          0 :   ret = sqlite3_exec( sqlite_handle, above41 ? "SELECT InitSpatialMetadata(1)" : "SELECT InitSpatialMetadata()", nullptr, nullptr, &errMsg );
     418                 :            : 
     419                 :          0 :   if ( ret != SQLITE_OK )
     420                 :            :   {
     421                 :          0 :     QString errCause = tr( "Unable to initialize SpatialMetadata:\n" );
     422                 :          0 :     errCause += QString::fromUtf8( errMsg );
     423                 :          0 :     showWarning( errCause );
     424                 :          0 :     sqlite3_free( errMsg );
     425                 :            :     return;
     426                 :          0 :   }
     427                 :          0 :   spatial_ref_sys_init( sqlite_handle, 0 );
     428                 :          0 : }
     429                 :            : 
     430                 :          0 : bool QgsOfflineEditing::createOfflineDb( const QString &offlineDbPath, ContainerType containerType )
     431                 :            : {
     432                 :            :   int ret;
     433                 :          0 :   char *errMsg = nullptr;
     434                 :          0 :   QFile newDb( offlineDbPath );
     435                 :          0 :   if ( newDb.exists() )
     436                 :            :   {
     437                 :          0 :     QFile::remove( offlineDbPath );
     438                 :          0 :   }
     439                 :            : 
     440                 :            :   // see also QgsNewSpatialiteLayerDialog::createDb()
     441                 :            : 
     442                 :          0 :   QFileInfo fullPath = QFileInfo( offlineDbPath );
     443                 :          0 :   QDir path = fullPath.dir();
     444                 :            : 
     445                 :            :   // Must be sure there is destination directory ~/.qgis
     446                 :          0 :   QDir().mkpath( path.absolutePath() );
     447                 :            : 
     448                 :            :   // creating/opening the new database
     449                 :          0 :   QString dbPath = newDb.fileName();
     450                 :            : 
     451                 :            :   // creating geopackage
     452                 :          0 :   switch ( containerType )
     453                 :            :   {
     454                 :            :     case GPKG:
     455                 :            :     {
     456                 :          0 :       OGRSFDriverH hGpkgDriver = OGRGetDriverByName( "GPKG" );
     457                 :          0 :       if ( !hGpkgDriver )
     458                 :            :       {
     459                 :          0 :         showWarning( tr( "Creation of database failed. GeoPackage driver not found." ) );
     460                 :          0 :         return false;
     461                 :            :       }
     462                 :            : 
     463                 :          0 :       gdal::ogr_datasource_unique_ptr hDS( OGR_Dr_CreateDataSource( hGpkgDriver, dbPath.toUtf8().constData(), nullptr ) );
     464                 :          0 :       if ( !hDS )
     465                 :            :       {
     466                 :          0 :         showWarning( tr( "Creation of database failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
     467                 :          0 :         return false;
     468                 :            :       }
     469                 :          0 :       break;
     470                 :          0 :     }
     471                 :            :     case SpatiaLite:
     472                 :            :     {
     473                 :          0 :       break;
     474                 :            :     }
     475                 :            :   }
     476                 :            : 
     477                 :          0 :   spatialite_database_unique_ptr database;
     478                 :          0 :   ret = database.open_v2( dbPath, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr );
     479                 :          0 :   if ( ret )
     480                 :            :   {
     481                 :            :     // an error occurred
     482                 :          0 :     QString errCause = tr( "Could not create a new database\n" );
     483                 :          0 :     errCause += database.errorMessage();
     484                 :          0 :     showWarning( errCause );
     485                 :          0 :     return false;
     486                 :          0 :   }
     487                 :            :   // activating Foreign Key constraints
     488                 :          0 :   ret = sqlite3_exec( database.get(), "PRAGMA foreign_keys = 1", nullptr, nullptr, &errMsg );
     489                 :          0 :   if ( ret != SQLITE_OK )
     490                 :            :   {
     491                 :          0 :     showWarning( tr( "Unable to activate FOREIGN_KEY constraints" ) );
     492                 :          0 :     sqlite3_free( errMsg );
     493                 :          0 :     return false;
     494                 :            :   }
     495                 :          0 :   initializeSpatialMetadata( database.get() );
     496                 :          0 :   return true;
     497                 :          0 : }
     498                 :            : 
     499                 :          0 : void QgsOfflineEditing::createLoggingTables( sqlite3 *db )
     500                 :            : {
     501                 :            :   // indices
     502                 :          0 :   QString sql = QStringLiteral( "CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)" );
     503                 :          0 :   sqlExec( db, sql );
     504                 :            : 
     505                 :          0 :   sql = QStringLiteral( "INSERT INTO 'log_indices' VALUES ('commit_no', 0)" );
     506                 :          0 :   sqlExec( db, sql );
     507                 :            : 
     508                 :          0 :   sql = QStringLiteral( "INSERT INTO 'log_indices' VALUES ('layer_id', 0)" );
     509                 :          0 :   sqlExec( db, sql );
     510                 :            : 
     511                 :            :   // layername <-> layer id
     512                 :          0 :   sql = QStringLiteral( "CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)" );
     513                 :          0 :   sqlExec( db, sql );
     514                 :            : 
     515                 :            :   // offline fid <-> remote fid
     516                 :          0 :   sql = QStringLiteral( "CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER)" );
     517                 :          0 :   sqlExec( db, sql );
     518                 :            : 
     519                 :            :   // added attributes
     520                 :          0 :   sql = QStringLiteral( "CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, " );
     521                 :          0 :   sql += QLatin1String( "'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)" );
     522                 :          0 :   sqlExec( db, sql );
     523                 :            : 
     524                 :            :   // added features
     525                 :          0 :   sql = QStringLiteral( "CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
     526                 :          0 :   sqlExec( db, sql );
     527                 :            : 
     528                 :            :   // removed features
     529                 :          0 :   sql = QStringLiteral( "CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
     530                 :          0 :   sqlExec( db, sql );
     531                 :            : 
     532                 :            :   // feature updates
     533                 :          0 :   sql = QStringLiteral( "CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)" );
     534                 :          0 :   sqlExec( db, sql );
     535                 :            : 
     536                 :            :   // geometry updates
     537                 :          0 :   sql = QStringLiteral( "CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)" );
     538                 :          0 :   sqlExec( db, sql );
     539                 :            : 
     540                 :            :   /* TODO: other logging tables
     541                 :            :     - attr delete (not supported by SpatiaLite provider)
     542                 :            :   */
     543                 :          0 : }
     544                 :            : 
     545                 :          0 : QgsVectorLayer *QgsOfflineEditing::copyVectorLayer( QgsVectorLayer *layer, sqlite3 *db, const QString &offlineDbPath, bool onlySelected, ContainerType containerType, const QString &layerNameSuffix )
     546                 :            : {
     547                 :          0 :   if ( !layer )
     548                 :          0 :     return nullptr;
     549                 :            : 
     550                 :          0 :   QString tableName = layer->id();
     551                 :          0 :   QgsDebugMsgLevel( QStringLiteral( "Creating offline table %1 ..." ).arg( tableName ), 4 );
     552                 :            : 
     553                 :            :   // new layer
     554                 :          0 :   QgsVectorLayer *newLayer = nullptr;
     555                 :            : 
     556                 :          0 :   switch ( containerType )
     557                 :            :   {
     558                 :            :     case SpatiaLite:
     559                 :            :     {
     560                 :            :       // create table
     561                 :          0 :       QString sql = QStringLiteral( "CREATE TABLE '%1' (" ).arg( tableName );
     562                 :          0 :       QString delim;
     563                 :          0 :       const QgsFields providerFields = layer->dataProvider()->fields();
     564                 :          0 :       for ( const auto &field : providerFields )
     565                 :            :       {
     566                 :          0 :         QString dataType;
     567                 :          0 :         QVariant::Type type = field.type();
     568                 :          0 :         if ( type == QVariant::Int || type == QVariant::LongLong )
     569                 :            :         {
     570                 :          0 :           dataType = QStringLiteral( "INTEGER" );
     571                 :          0 :         }
     572                 :          0 :         else if ( type == QVariant::Double )
     573                 :            :         {
     574                 :          0 :           dataType = QStringLiteral( "REAL" );
     575                 :          0 :         }
     576                 :          0 :         else if ( type == QVariant::String )
     577                 :            :         {
     578                 :          0 :           dataType = QStringLiteral( "TEXT" );
     579                 :          0 :         }
     580                 :            :         else
     581                 :            :         {
     582                 :          0 :           showWarning( tr( "%1: Unknown data type %2. Not using type affinity for the field." ).arg( field.name(), QVariant::typeToName( type ) ) );
     583                 :            :         }
     584                 :            : 
     585                 :          0 :         sql += delim + QStringLiteral( "'%1' %2" ).arg( field.name(), dataType );
     586                 :          0 :         delim = ',';
     587                 :          0 :       }
     588                 :          0 :       sql += ')';
     589                 :            : 
     590                 :          0 :       int rc = sqlExec( db, sql );
     591                 :            : 
     592                 :            :       // add geometry column
     593                 :          0 :       if ( layer->isSpatial() )
     594                 :            :       {
     595                 :          0 :         const QgsWkbTypes::Type sourceWkbType = layer->wkbType();
     596                 :            : 
     597                 :          0 :         QString geomType;
     598                 :          0 :         switch ( QgsWkbTypes::flatType( sourceWkbType ) )
     599                 :            :         {
     600                 :            :           case QgsWkbTypes::Point:
     601                 :          0 :             geomType = QStringLiteral( "POINT" );
     602                 :          0 :             break;
     603                 :            :           case QgsWkbTypes::MultiPoint:
     604                 :          0 :             geomType = QStringLiteral( "MULTIPOINT" );
     605                 :          0 :             break;
     606                 :            :           case QgsWkbTypes::LineString:
     607                 :          0 :             geomType = QStringLiteral( "LINESTRING" );
     608                 :          0 :             break;
     609                 :            :           case QgsWkbTypes::MultiLineString:
     610                 :          0 :             geomType = QStringLiteral( "MULTILINESTRING" );
     611                 :          0 :             break;
     612                 :            :           case QgsWkbTypes::Polygon:
     613                 :          0 :             geomType = QStringLiteral( "POLYGON" );
     614                 :          0 :             break;
     615                 :            :           case QgsWkbTypes::MultiPolygon:
     616                 :          0 :             geomType = QStringLiteral( "MULTIPOLYGON" );
     617                 :          0 :             break;
     618                 :            :           default:
     619                 :          0 :             showWarning( tr( "Layer %1 has unsupported geometry type %2." ).arg( layer->name(), QgsWkbTypes::displayString( layer->wkbType() ) ) );
     620                 :          0 :             break;
     621                 :            :         };
     622                 :            : 
     623                 :          0 :         QString zmInfo = QStringLiteral( "XY" );
     624                 :            : 
     625                 :          0 :         if ( QgsWkbTypes::hasZ( sourceWkbType ) )
     626                 :          0 :           zmInfo += 'Z';
     627                 :          0 :         if ( QgsWkbTypes::hasM( sourceWkbType ) )
     628                 :          0 :           zmInfo += 'M';
     629                 :            : 
     630                 :          0 :         QString epsgCode;
     631                 :            : 
     632                 :          0 :         if ( layer->crs().authid().startsWith( QLatin1String( "EPSG:" ), Qt::CaseInsensitive ) )
     633                 :            :         {
     634                 :          0 :           epsgCode = layer->crs().authid().mid( 5 );
     635                 :          0 :         }
     636                 :            :         else
     637                 :            :         {
     638                 :          0 :           epsgCode = '0';
     639                 :          0 :           showWarning( tr( "Layer %1 has unsupported Coordinate Reference System (%2)." ).arg( layer->name(), layer->crs().authid() ) );
     640                 :            :         }
     641                 :            : 
     642                 :          0 :         QString sqlAddGeom = QStringLiteral( "SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', '%4')" )
     643                 :          0 :                              .arg( tableName, epsgCode, geomType, zmInfo );
     644                 :            : 
     645                 :            :         // create spatial index
     646                 :          0 :         QString sqlCreateIndex = QStringLiteral( "SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName );
     647                 :            : 
     648                 :          0 :         if ( rc == SQLITE_OK )
     649                 :            :         {
     650                 :          0 :           rc = sqlExec( db, sqlAddGeom );
     651                 :          0 :           if ( rc == SQLITE_OK )
     652                 :            :           {
     653                 :          0 :             rc = sqlExec( db, sqlCreateIndex );
     654                 :          0 :           }
     655                 :          0 :         }
     656                 :          0 :       }
     657                 :            : 
     658                 :          0 :       if ( rc != SQLITE_OK )
     659                 :            :       {
     660                 :          0 :         showWarning( tr( "Filling SpatiaLite for layer %1 failed" ).arg( layer->name() ) );
     661                 :          0 :         return nullptr;
     662                 :            :       }
     663                 :            : 
     664                 :            :       // add new layer
     665                 :          0 :       QString connectionString = QStringLiteral( "dbname='%1' table='%2'%3 sql=" )
     666                 :          0 :                                  .arg( offlineDbPath,
     667                 :          0 :                                        tableName, layer->isSpatial() ? "(Geometry)" : "" );
     668                 :          0 :       QgsVectorLayer::LayerOptions options { QgsProject::instance()->transformContext() };
     669                 :          0 :       newLayer = new QgsVectorLayer( connectionString,
     670                 :          0 :                                      layer->name() + layerNameSuffix, QStringLiteral( "spatialite" ), options );
     671                 :            :       break;
     672                 :          0 :     }
     673                 :            :     case GPKG:
     674                 :            :     {
     675                 :            :       // Set options
     676                 :          0 :       char **options = nullptr;
     677                 :            : 
     678                 :          0 :       options = CSLSetNameValue( options, "OVERWRITE", "YES" );
     679                 :          0 :       options = CSLSetNameValue( options, "IDENTIFIER", tr( "%1 (offline)" ).arg( layer->id() ).toUtf8().constData() );
     680                 :          0 :       options = CSLSetNameValue( options, "DESCRIPTION", layer->dataComment().toUtf8().constData() );
     681                 :            : 
     682                 :            :       //the FID-name should not exist in the original data
     683                 :          0 :       QString fidBase( QStringLiteral( "fid" ) );
     684                 :          0 :       QString fid = fidBase;
     685                 :          0 :       int counter = 1;
     686                 :          0 :       while ( layer->dataProvider()->fields().lookupField( fid ) >= 0 && counter < 10000 )
     687                 :            :       {
     688                 :          0 :         fid = fidBase + '_' + QString::number( counter );
     689                 :          0 :         counter++;
     690                 :            :       }
     691                 :          0 :       if ( counter == 10000 )
     692                 :            :       {
     693                 :          0 :         showWarning( tr( "Cannot make FID-name for GPKG " ) );
     694                 :          0 :         return nullptr;
     695                 :            :       }
     696                 :            : 
     697                 :          0 :       options = CSLSetNameValue( options, "FID", fid.toUtf8().constData() );
     698                 :            : 
     699                 :          0 :       if ( layer->isSpatial() )
     700                 :            :       {
     701                 :          0 :         options = CSLSetNameValue( options, "GEOMETRY_COLUMN", "geom" );
     702                 :          0 :         options = CSLSetNameValue( options, "SPATIAL_INDEX", "YES" );
     703                 :          0 :       }
     704                 :            : 
     705                 :          0 :       OGRSFDriverH hDriver = nullptr;
     706                 :          0 :       OGRSpatialReferenceH hSRS = OSRNewSpatialReference( layer->crs().toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED_GDAL ).toLocal8Bit().data() );
     707                 :          0 :       gdal::ogr_datasource_unique_ptr hDS( OGROpen( offlineDbPath.toUtf8().constData(), true, &hDriver ) );
     708                 :          0 :       OGRLayerH hLayer = OGR_DS_CreateLayer( hDS.get(), tableName.toUtf8().constData(), hSRS, static_cast<OGRwkbGeometryType>( layer->wkbType() ), options );
     709                 :          0 :       CSLDestroy( options );
     710                 :          0 :       if ( hSRS )
     711                 :          0 :         OSRRelease( hSRS );
     712                 :          0 :       if ( !hLayer )
     713                 :            :       {
     714                 :          0 :         showWarning( tr( "Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
     715                 :          0 :         return nullptr;
     716                 :            :       }
     717                 :            : 
     718                 :          0 :       const QgsFields providerFields = layer->dataProvider()->fields();
     719                 :          0 :       for ( const auto &field : providerFields )
     720                 :            :       {
     721                 :          0 :         const QString fieldName( field.name() );
     722                 :          0 :         const QVariant::Type type = field.type();
     723                 :          0 :         OGRFieldType ogrType( OFTString );
     724                 :          0 :         OGRFieldSubType ogrSubType = OFSTNone;
     725                 :          0 :         if ( type == QVariant::Int )
     726                 :          0 :           ogrType = OFTInteger;
     727                 :          0 :         else if ( type == QVariant::LongLong )
     728                 :          0 :           ogrType = OFTInteger64;
     729                 :          0 :         else if ( type == QVariant::Double )
     730                 :          0 :           ogrType = OFTReal;
     731                 :          0 :         else if ( type == QVariant::Time )
     732                 :          0 :           ogrType = OFTTime;
     733                 :          0 :         else if ( type == QVariant::Date )
     734                 :          0 :           ogrType = OFTDate;
     735                 :          0 :         else if ( type == QVariant::DateTime )
     736                 :          0 :           ogrType = OFTDateTime;
     737                 :          0 :         else if ( type == QVariant::Bool )
     738                 :            :         {
     739                 :          0 :           ogrType = OFTInteger;
     740                 :          0 :           ogrSubType = OFSTBoolean;
     741                 :          0 :         }
     742                 :            :         else
     743                 :          0 :           ogrType = OFTString;
     744                 :            : 
     745                 :          0 :         int ogrWidth = field.length();
     746                 :            : 
     747                 :          0 :         gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( fieldName.toUtf8().constData(), ogrType ) );
     748                 :          0 :         OGR_Fld_SetWidth( fld.get(), ogrWidth );
     749                 :          0 :         if ( ogrSubType != OFSTNone )
     750                 :          0 :           OGR_Fld_SetSubType( fld.get(), ogrSubType );
     751                 :            : 
     752                 :          0 :         if ( OGR_L_CreateField( hLayer, fld.get(), true ) != OGRERR_NONE )
     753                 :            :         {
     754                 :          0 :           showWarning( tr( "Creation of field %1 failed (OGR error: %2)" )
     755                 :          0 :                        .arg( fieldName, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
     756                 :          0 :           return nullptr;
     757                 :            :         }
     758                 :          0 :       }
     759                 :            : 
     760                 :            :       // In GDAL >= 2.0, the driver implements a deferred creation strategy, so
     761                 :            :       // issue a command that will force table creation
     762                 :          0 :       CPLErrorReset();
     763                 :          0 :       OGR_L_ResetReading( hLayer );
     764                 :          0 :       if ( CPLGetLastErrorType() != CE_None )
     765                 :            :       {
     766                 :          0 :         QString msg( tr( "Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
     767                 :          0 :         showWarning( msg );
     768                 :          0 :         return nullptr;
     769                 :          0 :       }
     770                 :          0 :       hDS.reset();
     771                 :            : 
     772                 :          0 :       QString uri = QStringLiteral( "%1|layername=%2" ).arg( offlineDbPath,  tableName );
     773                 :          0 :       QgsVectorLayer::LayerOptions layerOptions { QgsProject::instance()->transformContext() };
     774                 :          0 :       newLayer = new QgsVectorLayer( uri, layer->name() + layerNameSuffix, QStringLiteral( "ogr" ), layerOptions );
     775                 :            :       break;
     776                 :          0 :     }
     777                 :            :   }
     778                 :            : 
     779                 :          0 :   if ( newLayer->isValid() )
     780                 :            :   {
     781                 :            : 
     782                 :            :     // copy features
     783                 :          0 :     newLayer->startEditing();
     784                 :          0 :     QgsFeature f;
     785                 :            : 
     786                 :          0 :     QgsFeatureRequest req;
     787                 :            : 
     788                 :          0 :     if ( onlySelected )
     789                 :            :     {
     790                 :          0 :       QgsFeatureIds selectedFids = layer->selectedFeatureIds();
     791                 :          0 :       if ( !selectedFids.isEmpty() )
     792                 :          0 :         req.setFilterFids( selectedFids );
     793                 :          0 :     }
     794                 :            : 
     795                 :          0 :     QgsFeatureIterator fit = layer->dataProvider()->getFeatures( req );
     796                 :            : 
     797                 :          0 :     if ( req.filterType() == QgsFeatureRequest::FilterFids )
     798                 :            :     {
     799                 :          0 :       emit progressModeSet( QgsOfflineEditing::CopyFeatures, layer->selectedFeatureIds().size() );
     800                 :          0 :     }
     801                 :            :     else
     802                 :            :     {
     803                 :          0 :       emit progressModeSet( QgsOfflineEditing::CopyFeatures, layer->dataProvider()->featureCount() );
     804                 :            :     }
     805                 :          0 :     int featureCount = 1;
     806                 :            : 
     807                 :          0 :     QList<QgsFeatureId> remoteFeatureIds;
     808                 :          0 :     while ( fit.nextFeature( f ) )
     809                 :            :     {
     810                 :          0 :       remoteFeatureIds << f.id();
     811                 :            : 
     812                 :            :       // NOTE: SpatiaLite provider ignores position of geometry column
     813                 :            :       // fill gap in QgsAttributeMap if geometry column is not last (WORKAROUND)
     814                 :          0 :       int column = 0;
     815                 :          0 :       QgsAttributes attrs = f.attributes();
     816                 :            :       // on GPKG newAttrs has an addition FID attribute, so we have to add a dummy in the original set
     817                 :          0 :       QgsAttributes newAttrs( containerType == GPKG ? attrs.count() + 1 : attrs.count() );
     818                 :          0 :       for ( int it = 0; it < attrs.count(); ++it )
     819                 :            :       {
     820                 :          0 :         newAttrs[column++] = attrs.at( it );
     821                 :          0 :       }
     822                 :          0 :       f.setAttributes( newAttrs );
     823                 :            : 
     824                 :          0 :       newLayer->addFeature( f );
     825                 :            : 
     826                 :          0 :       emit progressUpdated( featureCount++ );
     827                 :          0 :     }
     828                 :          0 :     if ( newLayer->commitChanges() )
     829                 :            :     {
     830                 :          0 :       emit progressModeSet( QgsOfflineEditing::ProcessFeatures, layer->dataProvider()->featureCount() );
     831                 :          0 :       featureCount = 1;
     832                 :            : 
     833                 :            :       // update feature id lookup
     834                 :          0 :       int layerId = getOrCreateLayerId( db, newLayer->id() );
     835                 :          0 :       QList<QgsFeatureId> offlineFeatureIds;
     836                 :            : 
     837                 :          0 :       QgsFeatureIterator fit = newLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setNoAttributes() );
     838                 :          0 :       while ( fit.nextFeature( f ) )
     839                 :            :       {
     840                 :          0 :         offlineFeatureIds << f.id();
     841                 :            :       }
     842                 :            : 
     843                 :            :       // NOTE: insert fids in this loop, as the db is locked during newLayer->nextFeature()
     844                 :          0 :       sqlExec( db, QStringLiteral( "BEGIN" ) );
     845                 :          0 :       int remoteCount = remoteFeatureIds.size();
     846                 :          0 :       for ( int i = 0; i < remoteCount; i++ )
     847                 :            :       {
     848                 :            :         // Check if the online feature has been fetched (WFS download aborted for some reason)
     849                 :          0 :         if ( i < offlineFeatureIds.count() )
     850                 :            :         {
     851                 :          0 :           addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( i ) );
     852                 :          0 :         }
     853                 :            :         else
     854                 :            :         {
     855                 :          0 :           showWarning( tr( "Feature cannot be copied to the offline layer, please check if the online layer '%1' is still accessible." ).arg( layer->name() ) );
     856                 :          0 :           return nullptr;
     857                 :            :         }
     858                 :          0 :         emit progressUpdated( featureCount++ );
     859                 :          0 :       }
     860                 :          0 :       sqlExec( db, QStringLiteral( "COMMIT" ) );
     861                 :          0 :     }
     862                 :            :     else
     863                 :            :     {
     864                 :          0 :       showWarning( newLayer->commitErrors().join( QLatin1Char( '\n' ) ) );
     865                 :            :     }
     866                 :            : 
     867                 :            :     // copy the custom properties from original layer
     868                 :          0 :     newLayer->setCustomProperties( layer->customProperties() );
     869                 :            : 
     870                 :            :     // mark as offline layer
     871                 :          0 :     newLayer->setCustomProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, true );
     872                 :            : 
     873                 :            :     // store original layer source and information
     874                 :          0 :     newLayer->setCustomProperty( CUSTOM_PROPERTY_REMOTE_SOURCE, layer->source() );
     875                 :          0 :     newLayer->setCustomProperty( CUSTOM_PROPERTY_REMOTE_PROVIDER, layer->providerType() );
     876                 :          0 :     newLayer->setCustomProperty( CUSTOM_PROPERTY_ORIGINAL_LAYERID, layer->id() );
     877                 :          0 :     newLayer->setCustomProperty( CUSTOM_PROPERTY_LAYERNAME_SUFFIX, layerNameSuffix );
     878                 :            : 
     879                 :            :     // register this layer with the central layers registry
     880                 :          0 :     QgsProject::instance()->addMapLayers(
     881                 :          0 :       QList<QgsMapLayer *>() << newLayer );
     882                 :            : 
     883                 :            :     // copy style
     884                 :          0 :     copySymbology( layer, newLayer );
     885                 :            : 
     886                 :            :     //remove constrainst of fields that use defaultValueClauses from provider on original
     887                 :          0 :     const auto fields = layer->fields();
     888                 :          0 :     for ( const QgsField &field : fields )
     889                 :            :     {
     890                 :          0 :       if ( !layer->dataProvider()->defaultValueClause( layer->fields().fieldOriginIndex( layer->fields().indexOf( field.name() ) ) ).isEmpty() )
     891                 :            :       {
     892                 :          0 :         newLayer->removeFieldConstraint( newLayer->fields().indexOf( field.name() ), QgsFieldConstraints::ConstraintNotNull );
     893                 :          0 :       }
     894                 :            :     }
     895                 :            : 
     896                 :          0 :     QgsLayerTreeGroup *layerTreeRoot = QgsProject::instance()->layerTreeRoot();
     897                 :            :     // Find the parent group of the original layer
     898                 :          0 :     QgsLayerTreeLayer *layerTreeLayer = layerTreeRoot->findLayer( layer->id() );
     899                 :          0 :     if ( layerTreeLayer )
     900                 :            :     {
     901                 :          0 :       QgsLayerTreeGroup *parentTreeGroup = qobject_cast<QgsLayerTreeGroup *>( layerTreeLayer->parent() );
     902                 :          0 :       if ( parentTreeGroup )
     903                 :            :       {
     904                 :          0 :         int index = parentTreeGroup->children().indexOf( layerTreeLayer );
     905                 :            :         // Move the new layer from the root group to the new group
     906                 :          0 :         QgsLayerTreeLayer *newLayerTreeLayer = layerTreeRoot->findLayer( newLayer->id() );
     907                 :          0 :         if ( newLayerTreeLayer )
     908                 :            :         {
     909                 :          0 :           QgsLayerTreeNode *newLayerTreeLayerClone = newLayerTreeLayer->clone();
     910                 :            :           //copy the showFeatureCount property to the new node
     911                 :          0 :           newLayerTreeLayerClone->setCustomProperty( CUSTOM_SHOW_FEATURE_COUNT, layerTreeLayer->customProperty( CUSTOM_SHOW_FEATURE_COUNT ) );
     912                 :          0 :           newLayerTreeLayerClone->setItemVisibilityChecked( layerTreeLayer->isVisible() );
     913                 :          0 :           QgsLayerTreeGroup *grp = qobject_cast<QgsLayerTreeGroup *>( newLayerTreeLayer->parent() );
     914                 :          0 :           parentTreeGroup->insertChildNode( index, newLayerTreeLayerClone );
     915                 :          0 :           if ( grp )
     916                 :          0 :             grp->removeChildNode( newLayerTreeLayer );
     917                 :          0 :         }
     918                 :          0 :       }
     919                 :          0 :     }
     920                 :            : 
     921                 :          0 :     updateRelations( layer, newLayer );
     922                 :          0 :     updateMapThemes( layer, newLayer );
     923                 :          0 :     updateLayerOrder( layer, newLayer );
     924                 :            : 
     925                 :            : 
     926                 :            : 
     927                 :          0 :   }
     928                 :          0 :   return newLayer;
     929                 :          0 : }
     930                 :            : 
     931                 :          0 : void QgsOfflineEditing::applyAttributesAdded( QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId, int commitNo )
     932                 :            : {
     933                 :          0 :   QString sql = QStringLiteral( "SELECT \"name\", \"type\", \"length\", \"precision\", \"comment\" FROM 'log_added_attrs' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
     934                 :          0 :   QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
     935                 :            : 
     936                 :          0 :   const QgsVectorDataProvider *provider = remoteLayer->dataProvider();
     937                 :          0 :   QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->nativeTypes();
     938                 :            : 
     939                 :            :   // NOTE: uses last matching QVariant::Type of nativeTypes
     940                 :          0 :   QMap < QVariant::Type, QString /*typeName*/ > typeNameLookup;
     941                 :          0 :   for ( int i = 0; i < nativeTypes.size(); i++ )
     942                 :            :   {
     943                 :          0 :     QgsVectorDataProvider::NativeType nativeType = nativeTypes.at( i );
     944                 :          0 :     typeNameLookup[ nativeType.mType ] = nativeType.mTypeName;
     945                 :          0 :   }
     946                 :            : 
     947                 :          0 :   emit progressModeSet( QgsOfflineEditing::AddFields, fields.size() );
     948                 :            : 
     949                 :          0 :   for ( int i = 0; i < fields.size(); i++ )
     950                 :            :   {
     951                 :            :     // lookup typename from layer provider
     952                 :          0 :     QgsField field = fields[i];
     953                 :          0 :     if ( typeNameLookup.contains( field.type() ) )
     954                 :            :     {
     955                 :          0 :       QString typeName = typeNameLookup[ field.type()];
     956                 :          0 :       field.setTypeName( typeName );
     957                 :          0 :       remoteLayer->addAttribute( field );
     958                 :          0 :     }
     959                 :            :     else
     960                 :            :     {
     961                 :          0 :       showWarning( QStringLiteral( "Could not add attribute '%1' of type %2" ).arg( field.name() ).arg( field.type() ) );
     962                 :            :     }
     963                 :            : 
     964                 :          0 :     emit progressUpdated( i + 1 );
     965                 :          0 :   }
     966                 :          0 : }
     967                 :            : 
     968                 :          0 : void QgsOfflineEditing::applyFeaturesAdded( QgsVectorLayer *offlineLayer, QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId )
     969                 :            : {
     970                 :          0 :   QString sql = QStringLiteral( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
     971                 :          0 :   const QList<int> featureIdInts = sqlQueryInts( db, sql );
     972                 :          0 :   QgsFeatureIds newFeatureIds;
     973                 :          0 :   for ( int id : featureIdInts )
     974                 :            :   {
     975                 :          0 :     newFeatureIds << id;
     976                 :            :   }
     977                 :            : 
     978                 :          0 :   QgsExpressionContext context = remoteLayer->createExpressionContext();
     979                 :            : 
     980                 :            :   // get new features from offline layer
     981                 :          0 :   QgsFeatureList features;
     982                 :          0 :   QgsFeatureIterator it = offlineLayer->getFeatures( QgsFeatureRequest().setFilterFids( newFeatureIds ) );
     983                 :          0 :   QgsFeature feature;
     984                 :          0 :   while ( it.nextFeature( feature ) )
     985                 :            :   {
     986                 :          0 :     features << feature;
     987                 :            :   }
     988                 :            : 
     989                 :            :   // copy features to remote layer
     990                 :          0 :   emit progressModeSet( QgsOfflineEditing::AddFeatures, features.size() );
     991                 :            : 
     992                 :          0 :   int i = 1;
     993                 :          0 :   int newAttrsCount = remoteLayer->fields().count();
     994                 :          0 :   for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
     995                 :            :   {
     996                 :            :     // NOTE: SpatiaLite provider ignores position of geometry column
     997                 :            :     // restore gap in QgsAttributeMap if geometry column is not last (WORKAROUND)
     998                 :          0 :     QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
     999                 :          0 :     QgsAttributes newAttrs( newAttrsCount );
    1000                 :          0 :     QgsAttributes attrs = it->attributes();
    1001                 :          0 :     for ( int it = 0; it < attrs.count(); ++it )
    1002                 :            :     {
    1003                 :          0 :       newAttrs[ attrLookup[ it ] ] = attrs.at( it );
    1004                 :          0 :     }
    1005                 :            : 
    1006                 :            :     // respect constraints and provider default values
    1007                 :          0 :     QgsFeature f = QgsVectorLayerUtils::createFeature( remoteLayer, it->geometry(), newAttrs.toMap(), &context );
    1008                 :          0 :     remoteLayer->addFeature( f );
    1009                 :            : 
    1010                 :          0 :     emit progressUpdated( i++ );
    1011                 :          0 :   }
    1012                 :          0 : }
    1013                 :            : 
    1014                 :          0 : void QgsOfflineEditing::applyFeaturesRemoved( QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId )
    1015                 :            : {
    1016                 :          0 :   QString sql = QStringLiteral( "SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
    1017                 :          0 :   QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql );
    1018                 :            : 
    1019                 :          0 :   emit progressModeSet( QgsOfflineEditing::RemoveFeatures, values.size() );
    1020                 :            : 
    1021                 :          0 :   int i = 1;
    1022                 :          0 :   for ( QgsFeatureIds::const_iterator it = values.constBegin(); it != values.constEnd(); ++it )
    1023                 :            :   {
    1024                 :          0 :     QgsFeatureId fid = remoteFid( db, layerId, *it );
    1025                 :          0 :     remoteLayer->deleteFeature( fid );
    1026                 :            : 
    1027                 :          0 :     emit progressUpdated( i++ );
    1028                 :          0 :   }
    1029                 :          0 : }
    1030                 :            : 
    1031                 :          0 : void QgsOfflineEditing::applyAttributeValueChanges( QgsVectorLayer *offlineLayer, QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId, int commitNo )
    1032                 :            : {
    1033                 :          0 :   QString sql = QStringLiteral( "SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
    1034                 :          0 :   AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );
    1035                 :            : 
    1036                 :          0 :   emit progressModeSet( QgsOfflineEditing::UpdateFeatures, values.size() );
    1037                 :            : 
    1038                 :          0 :   QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
    1039                 :            : 
    1040                 :          0 :   for ( int i = 0; i < values.size(); i++ )
    1041                 :            :   {
    1042                 :          0 :     QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid );
    1043                 :          0 :     QgsDebugMsgLevel( QStringLiteral( "Offline changeAttributeValue %1 = %2" ).arg( QString( attrLookup[ values.at( i ).attr ] ), values.at( i ).value ), 4 );
    1044                 :          0 :     remoteLayer->changeAttributeValue( fid, attrLookup[ values.at( i ).attr ], values.at( i ).value );
    1045                 :            : 
    1046                 :          0 :     emit progressUpdated( i + 1 );
    1047                 :          0 :   }
    1048                 :          0 : }
    1049                 :            : 
    1050                 :          0 : void QgsOfflineEditing::applyGeometryChanges( QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId, int commitNo )
    1051                 :            : {
    1052                 :          0 :   QString sql = QStringLiteral( "SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
    1053                 :          0 :   GeometryChanges values = sqlQueryGeometryChanges( db, sql );
    1054                 :            : 
    1055                 :          0 :   emit progressModeSet( QgsOfflineEditing::UpdateGeometries, values.size() );
    1056                 :            : 
    1057                 :          0 :   for ( int i = 0; i < values.size(); i++ )
    1058                 :            :   {
    1059                 :          0 :     QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid );
    1060                 :          0 :     QgsGeometry newGeom = QgsGeometry::fromWkt( values.at( i ).geom_wkt );
    1061                 :          0 :     remoteLayer->changeGeometry( fid, newGeom );
    1062                 :            : 
    1063                 :          0 :     emit progressUpdated( i + 1 );
    1064                 :          0 :   }
    1065                 :          0 : }
    1066                 :            : 
    1067                 :          0 : void QgsOfflineEditing::updateFidLookup( QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId )
    1068                 :            : {
    1069                 :            :   // update fid lookup for added features
    1070                 :            : 
    1071                 :            :   // get remote added fids
    1072                 :            :   // NOTE: use QMap for sorted fids
    1073                 :          0 :   QMap < QgsFeatureId, bool /*dummy*/ > newRemoteFids;
    1074                 :          0 :   QgsFeature f;
    1075                 :            : 
    1076                 :          0 :   QgsFeatureIterator fit = remoteLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setNoAttributes() );
    1077                 :            : 
    1078                 :          0 :   emit progressModeSet( QgsOfflineEditing::ProcessFeatures, remoteLayer->featureCount() );
    1079                 :            : 
    1080                 :          0 :   int i = 1;
    1081                 :          0 :   while ( fit.nextFeature( f ) )
    1082                 :            :   {
    1083                 :          0 :     if ( offlineFid( db, layerId, f.id() ) == -1 )
    1084                 :            :     {
    1085                 :          0 :       newRemoteFids[ f.id()] = true;
    1086                 :          0 :     }
    1087                 :            : 
    1088                 :          0 :     emit progressUpdated( i++ );
    1089                 :            :   }
    1090                 :            : 
    1091                 :            :   // get local added fids
    1092                 :            :   // NOTE: fids are sorted
    1093                 :          0 :   QString sql = QStringLiteral( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
    1094                 :          0 :   QList<int> newOfflineFids = sqlQueryInts( db, sql );
    1095                 :            : 
    1096                 :          0 :   if ( newRemoteFids.size() != newOfflineFids.size() )
    1097                 :            :   {
    1098                 :            :     //showWarning( QString( "Different number of new features on offline layer (%1) and remote layer (%2)" ).arg(newOfflineFids.size()).arg(newRemoteFids.size()) );
    1099                 :          0 :   }
    1100                 :            :   else
    1101                 :            :   {
    1102                 :            :     // add new fid lookups
    1103                 :          0 :     i = 0;
    1104                 :          0 :     sqlExec( db, QStringLiteral( "BEGIN" ) );
    1105                 :          0 :     for ( QMap<QgsFeatureId, bool>::const_iterator it = newRemoteFids.constBegin(); it != newRemoteFids.constEnd(); ++it )
    1106                 :            :     {
    1107                 :          0 :       addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key() );
    1108                 :          0 :     }
    1109                 :          0 :     sqlExec( db, QStringLiteral( "COMMIT" ) );
    1110                 :            :   }
    1111                 :          0 : }
    1112                 :            : 
    1113                 :          0 : void QgsOfflineEditing::copySymbology( QgsVectorLayer *sourceLayer, QgsVectorLayer *targetLayer )
    1114                 :            : {
    1115                 :          0 :   targetLayer->styleManager()->copyStylesFrom( sourceLayer->styleManager() );
    1116                 :            : 
    1117                 :          0 :   QString error;
    1118                 :          0 :   QDomDocument doc;
    1119                 :          0 :   QgsReadWriteContext context;
    1120                 :          0 :   QgsMapLayer::StyleCategories categories = static_cast<QgsMapLayer::StyleCategories>( QgsMapLayer::AllStyleCategories ) & ~QgsMapLayer::CustomProperties;
    1121                 :          0 :   sourceLayer->exportNamedStyle( doc, error, context, categories );
    1122                 :            : 
    1123                 :          0 :   if ( error.isEmpty() )
    1124                 :            :   {
    1125                 :          0 :     targetLayer->importNamedStyle( doc, error, categories );
    1126                 :          0 :   }
    1127                 :          0 :   if ( !error.isEmpty() )
    1128                 :            :   {
    1129                 :          0 :     showWarning( error );
    1130                 :          0 :   }
    1131                 :          0 : }
    1132                 :            : 
    1133                 :          0 : void QgsOfflineEditing::updateRelations( QgsVectorLayer *sourceLayer, QgsVectorLayer *targetLayer )
    1134                 :            : {
    1135                 :          0 :   QgsRelationManager *relationManager = QgsProject::instance()->relationManager();
    1136                 :          0 :   const QList<QgsRelation> referencedRelations = relationManager->referencedRelations( sourceLayer );
    1137                 :            : 
    1138                 :          0 :   for ( QgsRelation relation : referencedRelations )
    1139                 :            :   {
    1140                 :          0 :     relationManager->removeRelation( relation );
    1141                 :          0 :     relation.setReferencedLayer( targetLayer->id() );
    1142                 :          0 :     relationManager->addRelation( relation );
    1143                 :          0 :   }
    1144                 :            : 
    1145                 :          0 :   const QList<QgsRelation> referencingRelations = relationManager->referencingRelations( sourceLayer );
    1146                 :            : 
    1147                 :          0 :   for ( QgsRelation relation : referencingRelations )
    1148                 :            :   {
    1149                 :          0 :     relationManager->removeRelation( relation );
    1150                 :          0 :     relation.setReferencingLayer( targetLayer->id() );
    1151                 :          0 :     relationManager->addRelation( relation );
    1152                 :          0 :   }
    1153                 :          0 : }
    1154                 :            : 
    1155                 :          0 : void QgsOfflineEditing::updateMapThemes( QgsVectorLayer *sourceLayer, QgsVectorLayer *targetLayer )
    1156                 :            : {
    1157                 :          0 :   QgsMapThemeCollection *mapThemeCollection = QgsProject::instance()->mapThemeCollection();
    1158                 :          0 :   const QStringList mapThemeNames = mapThemeCollection->mapThemes();
    1159                 :            : 
    1160                 :          0 :   for ( const QString &mapThemeName : mapThemeNames )
    1161                 :            :   {
    1162                 :          0 :     QgsMapThemeCollection::MapThemeRecord record = mapThemeCollection->mapThemeState( mapThemeName );
    1163                 :            : 
    1164                 :          0 :     const auto layerRecords = record.layerRecords();
    1165                 :            : 
    1166                 :          0 :     for ( QgsMapThemeCollection::MapThemeLayerRecord layerRecord : layerRecords )
    1167                 :            :     {
    1168                 :          0 :       if ( layerRecord.layer() == sourceLayer )
    1169                 :            :       {
    1170                 :          0 :         layerRecord.setLayer( targetLayer );
    1171                 :          0 :         record.removeLayerRecord( sourceLayer );
    1172                 :          0 :         record.addLayerRecord( layerRecord );
    1173                 :          0 :       }
    1174                 :          0 :     }
    1175                 :            : 
    1176                 :          0 :     QgsProject::instance()->mapThemeCollection()->update( mapThemeName, record );
    1177                 :          0 :   }
    1178                 :          0 : }
    1179                 :            : 
    1180                 :          0 : void QgsOfflineEditing::updateLayerOrder( QgsVectorLayer *sourceLayer, QgsVectorLayer *targetLayer )
    1181                 :            : {
    1182                 :          0 :   QList<QgsMapLayer *>  layerOrder = QgsProject::instance()->layerTreeRoot()->customLayerOrder();
    1183                 :            : 
    1184                 :          0 :   auto iterator = layerOrder.begin();
    1185                 :            : 
    1186                 :          0 :   while ( iterator != layerOrder.end() )
    1187                 :            :   {
    1188                 :          0 :     if ( *iterator == targetLayer )
    1189                 :            :     {
    1190                 :          0 :       iterator = layerOrder.erase( iterator );
    1191                 :          0 :       if ( iterator == layerOrder.end() )
    1192                 :          0 :         break;
    1193                 :          0 :     }
    1194                 :            : 
    1195                 :          0 :     if ( *iterator == sourceLayer )
    1196                 :            :     {
    1197                 :          0 :       *iterator = targetLayer;
    1198                 :          0 :     }
    1199                 :            : 
    1200                 :          0 :     ++iterator;
    1201                 :            :   }
    1202                 :            : 
    1203                 :          0 :   QgsProject::instance()->layerTreeRoot()->setCustomLayerOrder( layerOrder );
    1204                 :          0 : }
    1205                 :            : 
    1206                 :            : // NOTE: use this to map column indices in case the remote geometry column is not last
    1207                 :          0 : QMap<int, int> QgsOfflineEditing::attributeLookup( QgsVectorLayer *offlineLayer, QgsVectorLayer *remoteLayer )
    1208                 :            : {
    1209                 :          0 :   const QgsAttributeList &offlineAttrs = offlineLayer->attributeList();
    1210                 :            : 
    1211                 :          0 :   QMap < int /*offline attr*/, int /*remote attr*/ > attrLookup;
    1212                 :            :   // NOTE: though offlineAttrs can have new attributes not yet synced, we take the amount of offlineAttrs
    1213                 :            :   // because we anyway only add mapping for the fields existing in remoteLayer (this because it could contain fid on 0)
    1214                 :          0 :   for ( int i = 0; i < offlineAttrs.size(); i++ )
    1215                 :            :   {
    1216                 :          0 :     if ( remoteLayer->fields().lookupField( offlineLayer->fields().field( i ).name() ) >= 0 )
    1217                 :          0 :       attrLookup.insert( offlineAttrs.at( i ), remoteLayer->fields().indexOf( offlineLayer->fields().field( i ).name() ) );
    1218                 :          0 :   }
    1219                 :            : 
    1220                 :          0 :   return attrLookup;
    1221                 :          0 : }
    1222                 :            : 
    1223                 :          0 : void QgsOfflineEditing::showWarning( const QString &message )
    1224                 :            : {
    1225                 :          0 :   emit warning( tr( "Offline Editing Plugin" ), message );
    1226                 :          0 : }
    1227                 :            : 
    1228                 :          0 : sqlite3_database_unique_ptr QgsOfflineEditing::openLoggingDb()
    1229                 :            : {
    1230                 :          0 :   sqlite3_database_unique_ptr database;
    1231                 :          0 :   QString dbPath = QgsProject::instance()->readEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH );
    1232                 :          0 :   if ( !dbPath.isEmpty() )
    1233                 :            :   {
    1234                 :          0 :     QString absoluteDbPath = QgsProject::instance()->readPath( dbPath );
    1235                 :          0 :     int rc = database.open( absoluteDbPath );
    1236                 :          0 :     if ( rc != SQLITE_OK )
    1237                 :            :     {
    1238                 :          0 :       QgsDebugMsg( QStringLiteral( "Could not open the SpatiaLite logging database" ) );
    1239                 :          0 :       showWarning( tr( "Could not open the SpatiaLite logging database" ) );
    1240                 :          0 :     }
    1241                 :          0 :   }
    1242                 :            :   else
    1243                 :            :   {
    1244                 :          0 :     QgsDebugMsg( QStringLiteral( "dbPath is empty!" ) );
    1245                 :            :   }
    1246                 :          0 :   return database;
    1247                 :          0 : }
    1248                 :            : 
    1249                 :          0 : int QgsOfflineEditing::getOrCreateLayerId( sqlite3 *db, const QString &qgisLayerId )
    1250                 :            : {
    1251                 :          0 :   QString sql = QStringLiteral( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
    1252                 :          0 :   int layerId = sqlQueryInt( db, sql, -1 );
    1253                 :          0 :   if ( layerId == -1 )
    1254                 :            :   {
    1255                 :            :     // next layer id
    1256                 :          0 :     sql = QStringLiteral( "SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'" );
    1257                 :          0 :     int newLayerId = sqlQueryInt( db, sql, -1 );
    1258                 :            : 
    1259                 :            :     // insert layer
    1260                 :          0 :     sql = QStringLiteral( "INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId );
    1261                 :          0 :     sqlExec( db, sql );
    1262                 :            : 
    1263                 :            :     // increase layer_id
    1264                 :            :     // TODO: use trigger for auto increment?
    1265                 :          0 :     sql = QStringLiteral( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 );
    1266                 :          0 :     sqlExec( db, sql );
    1267                 :            : 
    1268                 :          0 :     layerId = newLayerId;
    1269                 :          0 :   }
    1270                 :            : 
    1271                 :          0 :   return layerId;
    1272                 :          0 : }
    1273                 :            : 
    1274                 :          0 : int QgsOfflineEditing::getCommitNo( sqlite3 *db )
    1275                 :            : {
    1276                 :          0 :   QString sql = QStringLiteral( "SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'" );
    1277                 :          0 :   return sqlQueryInt( db, sql, -1 );
    1278                 :          0 : }
    1279                 :            : 
    1280                 :          0 : void QgsOfflineEditing::increaseCommitNo( sqlite3 *db )
    1281                 :            : {
    1282                 :          0 :   QString sql = QStringLiteral( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg( getCommitNo( db ) + 1 );
    1283                 :          0 :   sqlExec( db, sql );
    1284                 :          0 : }
    1285                 :            : 
    1286                 :          0 : void QgsOfflineEditing::addFidLookup( sqlite3 *db, int layerId, QgsFeatureId offlineFid, QgsFeatureId remoteFid )
    1287                 :            : {
    1288                 :          0 :   QString sql = QStringLiteral( "INSERT INTO 'log_fids' VALUES ( %1, %2, %3 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid );
    1289                 :          0 :   sqlExec( db, sql );
    1290                 :          0 : }
    1291                 :            : 
    1292                 :          0 : QgsFeatureId QgsOfflineEditing::remoteFid( sqlite3 *db, int layerId, QgsFeatureId offlineFid )
    1293                 :            : {
    1294                 :          0 :   QString sql = QStringLiteral( "SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
    1295                 :          0 :   return sqlQueryInt( db, sql, -1 );
    1296                 :          0 : }
    1297                 :            : 
    1298                 :          0 : QgsFeatureId QgsOfflineEditing::offlineFid( sqlite3 *db, int layerId, QgsFeatureId remoteFid )
    1299                 :            : {
    1300                 :          0 :   QString sql = QStringLiteral( "SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid );
    1301                 :          0 :   return sqlQueryInt( db, sql, -1 );
    1302                 :          0 : }
    1303                 :            : 
    1304                 :          0 : bool QgsOfflineEditing::isAddedFeature( sqlite3 *db, int layerId, QgsFeatureId fid )
    1305                 :            : {
    1306                 :          0 :   QString sql = QStringLiteral( "SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid );
    1307                 :          0 :   return ( sqlQueryInt( db, sql, 0 ) > 0 );
    1308                 :          0 : }
    1309                 :            : 
    1310                 :          0 : int QgsOfflineEditing::sqlExec( sqlite3 *db, const QString &sql )
    1311                 :            : {
    1312                 :          0 :   char *errmsg = nullptr;
    1313                 :          0 :   int rc = sqlite3_exec( db, sql.toUtf8(), nullptr, nullptr, &errmsg );
    1314                 :          0 :   if ( rc != SQLITE_OK )
    1315                 :            :   {
    1316                 :          0 :     showWarning( errmsg );
    1317                 :          0 :   }
    1318                 :          0 :   return rc;
    1319                 :          0 : }
    1320                 :            : 
    1321                 :          0 : int QgsOfflineEditing::sqlQueryInt( sqlite3 *db, const QString &sql, int defaultValue )
    1322                 :            : {
    1323                 :          0 :   sqlite3_stmt *stmt = nullptr;
    1324                 :          0 :   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
    1325                 :            :   {
    1326                 :          0 :     showWarning( sqlite3_errmsg( db ) );
    1327                 :          0 :     return defaultValue;
    1328                 :            :   }
    1329                 :            : 
    1330                 :          0 :   int value = defaultValue;
    1331                 :          0 :   int ret = sqlite3_step( stmt );
    1332                 :          0 :   if ( ret == SQLITE_ROW )
    1333                 :            :   {
    1334                 :          0 :     value = sqlite3_column_int( stmt, 0 );
    1335                 :          0 :   }
    1336                 :          0 :   sqlite3_finalize( stmt );
    1337                 :            : 
    1338                 :          0 :   return value;
    1339                 :          0 : }
    1340                 :            : 
    1341                 :          0 : QList<int> QgsOfflineEditing::sqlQueryInts( sqlite3 *db, const QString &sql )
    1342                 :            : {
    1343                 :          0 :   QList<int> values;
    1344                 :            : 
    1345                 :          0 :   sqlite3_stmt *stmt = nullptr;
    1346                 :          0 :   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
    1347                 :            :   {
    1348                 :          0 :     showWarning( sqlite3_errmsg( db ) );
    1349                 :          0 :     return values;
    1350                 :            :   }
    1351                 :            : 
    1352                 :          0 :   int ret = sqlite3_step( stmt );
    1353                 :          0 :   while ( ret == SQLITE_ROW )
    1354                 :            :   {
    1355                 :          0 :     values << sqlite3_column_int( stmt, 0 );
    1356                 :            : 
    1357                 :          0 :     ret = sqlite3_step( stmt );
    1358                 :            :   }
    1359                 :          0 :   sqlite3_finalize( stmt );
    1360                 :            : 
    1361                 :          0 :   return values;
    1362                 :          0 : }
    1363                 :            : 
    1364                 :          0 : QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded( sqlite3 *db, const QString &sql )
    1365                 :            : {
    1366                 :          0 :   QList<QgsField> values;
    1367                 :            : 
    1368                 :          0 :   sqlite3_stmt *stmt = nullptr;
    1369                 :          0 :   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
    1370                 :            :   {
    1371                 :          0 :     showWarning( sqlite3_errmsg( db ) );
    1372                 :          0 :     return values;
    1373                 :            :   }
    1374                 :            : 
    1375                 :          0 :   int ret = sqlite3_step( stmt );
    1376                 :          0 :   while ( ret == SQLITE_ROW )
    1377                 :            :   {
    1378                 :          0 :     QgsField field( QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 0 ) ) ),
    1379                 :          0 :                     static_cast< QVariant::Type >( sqlite3_column_int( stmt, 1 ) ),
    1380                 :          0 :                     QString(), // typeName
    1381                 :          0 :                     sqlite3_column_int( stmt, 2 ),
    1382                 :          0 :                     sqlite3_column_int( stmt, 3 ),
    1383                 :          0 :                     QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 4 ) ) ) );
    1384                 :          0 :     values << field;
    1385                 :            : 
    1386                 :          0 :     ret = sqlite3_step( stmt );
    1387                 :          0 :   }
    1388                 :          0 :   sqlite3_finalize( stmt );
    1389                 :            : 
    1390                 :          0 :   return values;
    1391                 :          0 : }
    1392                 :            : 
    1393                 :          0 : QgsFeatureIds QgsOfflineEditing::sqlQueryFeaturesRemoved( sqlite3 *db, const QString &sql )
    1394                 :            : {
    1395                 :          0 :   QgsFeatureIds values;
    1396                 :            : 
    1397                 :          0 :   sqlite3_stmt *stmt = nullptr;
    1398                 :          0 :   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
    1399                 :            :   {
    1400                 :          0 :     showWarning( sqlite3_errmsg( db ) );
    1401                 :          0 :     return values;
    1402                 :            :   }
    1403                 :            : 
    1404                 :          0 :   int ret = sqlite3_step( stmt );
    1405                 :          0 :   while ( ret == SQLITE_ROW )
    1406                 :            :   {
    1407                 :          0 :     values << sqlite3_column_int( stmt, 0 );
    1408                 :            : 
    1409                 :          0 :     ret = sqlite3_step( stmt );
    1410                 :            :   }
    1411                 :          0 :   sqlite3_finalize( stmt );
    1412                 :            : 
    1413                 :          0 :   return values;
    1414                 :          0 : }
    1415                 :            : 
    1416                 :          0 : QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges( sqlite3 *db, const QString &sql )
    1417                 :            : {
    1418                 :          0 :   AttributeValueChanges values;
    1419                 :            : 
    1420                 :          0 :   sqlite3_stmt *stmt = nullptr;
    1421                 :          0 :   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
    1422                 :            :   {
    1423                 :          0 :     showWarning( sqlite3_errmsg( db ) );
    1424                 :          0 :     return values;
    1425                 :            :   }
    1426                 :            : 
    1427                 :          0 :   int ret = sqlite3_step( stmt );
    1428                 :          0 :   while ( ret == SQLITE_ROW )
    1429                 :            :   {
    1430                 :          0 :     AttributeValueChange change;
    1431                 :          0 :     change.fid = sqlite3_column_int( stmt, 0 );
    1432                 :          0 :     change.attr = sqlite3_column_int( stmt, 1 );
    1433                 :          0 :     change.value = QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 2 ) ) );
    1434                 :          0 :     values << change;
    1435                 :            : 
    1436                 :          0 :     ret = sqlite3_step( stmt );
    1437                 :          0 :   }
    1438                 :          0 :   sqlite3_finalize( stmt );
    1439                 :            : 
    1440                 :          0 :   return values;
    1441                 :          0 : }
    1442                 :            : 
    1443                 :          0 : QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges( sqlite3 *db, const QString &sql )
    1444                 :            : {
    1445                 :          0 :   GeometryChanges values;
    1446                 :            : 
    1447                 :          0 :   sqlite3_stmt *stmt = nullptr;
    1448                 :          0 :   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
    1449                 :            :   {
    1450                 :          0 :     showWarning( sqlite3_errmsg( db ) );
    1451                 :          0 :     return values;
    1452                 :            :   }
    1453                 :            : 
    1454                 :          0 :   int ret = sqlite3_step( stmt );
    1455                 :          0 :   while ( ret == SQLITE_ROW )
    1456                 :            :   {
    1457                 :          0 :     GeometryChange change;
    1458                 :          0 :     change.fid = sqlite3_column_int( stmt, 0 );
    1459                 :          0 :     change.geom_wkt = QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 1 ) ) );
    1460                 :          0 :     values << change;
    1461                 :            : 
    1462                 :          0 :     ret = sqlite3_step( stmt );
    1463                 :          0 :   }
    1464                 :          0 :   sqlite3_finalize( stmt );
    1465                 :            : 
    1466                 :          0 :   return values;
    1467                 :          0 : }
    1468                 :            : 
    1469                 :          0 : void QgsOfflineEditing::committedAttributesAdded( const QString &qgisLayerId, const QList<QgsField> &addedAttributes )
    1470                 :            : {
    1471                 :          0 :   sqlite3_database_unique_ptr database = openLoggingDb();
    1472                 :          0 :   if ( !database )
    1473                 :          0 :     return;
    1474                 :            : 
    1475                 :            :   // insert log
    1476                 :          0 :   int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
    1477                 :          0 :   int commitNo = getCommitNo( database.get() );
    1478                 :            : 
    1479                 :          0 :   for ( const QgsField &field : addedAttributes )
    1480                 :            :   {
    1481                 :          0 :     QString sql = QStringLiteral( "INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" )
    1482                 :          0 :                   .arg( layerId )
    1483                 :          0 :                   .arg( commitNo )
    1484                 :          0 :                   .arg( field.name() )
    1485                 :          0 :                   .arg( field.type() )
    1486                 :          0 :                   .arg( field.length() )
    1487                 :          0 :                   .arg( field.precision() )
    1488                 :          0 :                   .arg( field.comment() );
    1489                 :          0 :     sqlExec( database.get(), sql );
    1490                 :          0 :   }
    1491                 :            : 
    1492                 :          0 :   increaseCommitNo( database.get() );
    1493                 :          0 : }
    1494                 :            : 
    1495                 :          0 : void QgsOfflineEditing::committedFeaturesAdded( const QString &qgisLayerId, const QgsFeatureList &addedFeatures )
    1496                 :            : {
    1497                 :          0 :   sqlite3_database_unique_ptr database = openLoggingDb();
    1498                 :          0 :   if ( !database )
    1499                 :          0 :     return;
    1500                 :            : 
    1501                 :            :   // insert log
    1502                 :          0 :   int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
    1503                 :            : 
    1504                 :            :   // get new feature ids from db
    1505                 :          0 :   QgsMapLayer *layer = QgsProject::instance()->mapLayer( qgisLayerId );
    1506                 :          0 :   QString dataSourceString = layer->source();
    1507                 :          0 :   QgsDataSourceUri uri = QgsDataSourceUri( dataSourceString );
    1508                 :            : 
    1509                 :          0 :   QString offlinePath = QgsProject::instance()->readPath( QgsProject::instance()->readEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH ) );
    1510                 :          0 :   QString tableName;
    1511                 :            : 
    1512                 :          0 :   if ( !offlinePath.contains( ".gpkg" ) )
    1513                 :            :   {
    1514                 :          0 :     tableName = uri.table();
    1515                 :          0 :   }
    1516                 :            :   else
    1517                 :            :   {
    1518                 :          0 :     QgsProviderMetadata *ogrProviderMetaData = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "ogr" ) );
    1519                 :          0 :     QVariantMap decodedUri = ogrProviderMetaData->decodeUri( dataSourceString );
    1520                 :          0 :     tableName = decodedUri.value( QStringLiteral( "layerName" ) ).toString();
    1521                 :          0 :     if ( tableName.isEmpty() )
    1522                 :            :     {
    1523                 :          0 :       showWarning( tr( "Could not deduce table name from data source %1." ).arg( dataSourceString ) );
    1524                 :          0 :     }
    1525                 :          0 :   }
    1526                 :            : 
    1527                 :            :   // only store feature ids
    1528                 :          0 :   QString sql = QStringLiteral( "SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( tableName ).arg( addedFeatures.size() );
    1529                 :          0 :   QList<int> newFeatureIds = sqlQueryInts( database.get(), sql );
    1530                 :          0 :   for ( int i = newFeatureIds.size() - 1; i >= 0; i-- )
    1531                 :            :   {
    1532                 :          0 :     QString sql = QStringLiteral( "INSERT INTO 'log_added_features' VALUES ( %1, %2 )" )
    1533                 :          0 :                   .arg( layerId )
    1534                 :          0 :                   .arg( newFeatureIds.at( i ) );
    1535                 :          0 :     sqlExec( database.get(), sql );
    1536                 :          0 :   }
    1537                 :          0 : }
    1538                 :            : 
    1539                 :          0 : void QgsOfflineEditing::committedFeaturesRemoved( const QString &qgisLayerId, const QgsFeatureIds &deletedFeatureIds )
    1540                 :            : {
    1541                 :          0 :   sqlite3_database_unique_ptr database = openLoggingDb();
    1542                 :          0 :   if ( !database )
    1543                 :          0 :     return;
    1544                 :            : 
    1545                 :            :   // insert log
    1546                 :          0 :   int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
    1547                 :            : 
    1548                 :          0 :   for ( QgsFeatureId id : deletedFeatureIds )
    1549                 :            :   {
    1550                 :          0 :     if ( isAddedFeature( database.get(), layerId, id ) )
    1551                 :            :     {
    1552                 :            :       // remove from added features log
    1553                 :          0 :       QString sql = QStringLiteral( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( id );
    1554                 :          0 :       sqlExec( database.get(), sql );
    1555                 :          0 :     }
    1556                 :            :     else
    1557                 :            :     {
    1558                 :          0 :       QString sql = QStringLiteral( "INSERT INTO 'log_removed_features' VALUES ( %1, %2)" )
    1559                 :          0 :                     .arg( layerId )
    1560                 :          0 :                     .arg( id );
    1561                 :          0 :       sqlExec( database.get(), sql );
    1562                 :          0 :     }
    1563                 :            :   }
    1564                 :          0 : }
    1565                 :            : 
    1566                 :          0 : void QgsOfflineEditing::committedAttributeValuesChanges( const QString &qgisLayerId, const QgsChangedAttributesMap &changedAttrsMap )
    1567                 :            : {
    1568                 :          0 :   sqlite3_database_unique_ptr database = openLoggingDb();
    1569                 :          0 :   if ( !database )
    1570                 :          0 :     return;
    1571                 :            : 
    1572                 :            :   // insert log
    1573                 :          0 :   int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
    1574                 :          0 :   int commitNo = getCommitNo( database.get() );
    1575                 :            : 
    1576                 :          0 :   for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
    1577                 :            :   {
    1578                 :          0 :     QgsFeatureId fid = cit.key();
    1579                 :          0 :     if ( isAddedFeature( database.get(), layerId, fid ) )
    1580                 :            :     {
    1581                 :            :       // skip added features
    1582                 :          0 :       continue;
    1583                 :            :     }
    1584                 :          0 :     QgsAttributeMap attrMap = cit.value();
    1585                 :          0 :     for ( QgsAttributeMap::const_iterator it = attrMap.constBegin(); it != attrMap.constEnd(); ++it )
    1586                 :            :     {
    1587                 :          0 :       QString sql = QStringLiteral( "INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" )
    1588                 :          0 :                     .arg( layerId )
    1589                 :          0 :                     .arg( commitNo )
    1590                 :          0 :                     .arg( fid )
    1591                 :          0 :                     .arg( it.key() ) // attr
    1592                 :          0 :                     .arg( it.value().toString() ); // value
    1593                 :          0 :       sqlExec( database.get(), sql );
    1594                 :          0 :     }
    1595                 :          0 :   }
    1596                 :            : 
    1597                 :          0 :   increaseCommitNo( database.get() );
    1598                 :          0 : }
    1599                 :            : 
    1600                 :          0 : void QgsOfflineEditing::committedGeometriesChanges( const QString &qgisLayerId, const QgsGeometryMap &changedGeometries )
    1601                 :            : {
    1602                 :          0 :   sqlite3_database_unique_ptr database = openLoggingDb();
    1603                 :          0 :   if ( !database )
    1604                 :          0 :     return;
    1605                 :            : 
    1606                 :            :   // insert log
    1607                 :          0 :   int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
    1608                 :          0 :   int commitNo = getCommitNo( database.get() );
    1609                 :            : 
    1610                 :          0 :   for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
    1611                 :            :   {
    1612                 :          0 :     QgsFeatureId fid = it.key();
    1613                 :          0 :     if ( isAddedFeature( database.get(), layerId, fid ) )
    1614                 :            :     {
    1615                 :            :       // skip added features
    1616                 :          0 :       continue;
    1617                 :            :     }
    1618                 :          0 :     QgsGeometry geom = it.value();
    1619                 :          0 :     QString sql = QStringLiteral( "INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" )
    1620                 :          0 :                   .arg( layerId )
    1621                 :          0 :                   .arg( commitNo )
    1622                 :          0 :                   .arg( fid )
    1623                 :          0 :                   .arg( geom.asWkt() );
    1624                 :          0 :     sqlExec( database.get(), sql );
    1625                 :            : 
    1626                 :            :     // TODO: use WKB instead of WKT?
    1627                 :          0 :   }
    1628                 :            : 
    1629                 :          0 :   increaseCommitNo( database.get() );
    1630                 :          0 : }
    1631                 :            : 
    1632                 :          0 : void QgsOfflineEditing::startListenFeatureChanges()
    1633                 :            : {
    1634                 :          0 :   QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
    1635                 :            :   // enable logging, check if editBuffer is not null
    1636                 :          0 :   if ( vLayer->editBuffer() )
    1637                 :            :   {
    1638                 :          0 :     QgsVectorLayerEditBuffer *editBuffer = vLayer->editBuffer();
    1639                 :          0 :     connect( editBuffer, &QgsVectorLayerEditBuffer::committedAttributesAdded,
    1640                 :            :              this, &QgsOfflineEditing::committedAttributesAdded );
    1641                 :          0 :     connect( editBuffer, &QgsVectorLayerEditBuffer::committedAttributeValuesChanges,
    1642                 :            :              this, &QgsOfflineEditing::committedAttributeValuesChanges );
    1643                 :          0 :     connect( editBuffer, &QgsVectorLayerEditBuffer::committedGeometriesChanges,
    1644                 :            :              this, &QgsOfflineEditing::committedGeometriesChanges );
    1645                 :          0 :   }
    1646                 :          0 :   connect( vLayer, &QgsVectorLayer::committedFeaturesAdded,
    1647                 :            :            this, &QgsOfflineEditing::committedFeaturesAdded );
    1648                 :          0 :   connect( vLayer, &QgsVectorLayer::committedFeaturesRemoved,
    1649                 :            :            this, &QgsOfflineEditing::committedFeaturesRemoved );
    1650                 :          0 : }
    1651                 :            : 
    1652                 :          0 : void QgsOfflineEditing::stopListenFeatureChanges()
    1653                 :            : {
    1654                 :          0 :   QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
    1655                 :            :   // disable logging, check if editBuffer is not null
    1656                 :          0 :   if ( vLayer->editBuffer() )
    1657                 :            :   {
    1658                 :          0 :     QgsVectorLayerEditBuffer *editBuffer = vLayer->editBuffer();
    1659                 :          0 :     disconnect( editBuffer, &QgsVectorLayerEditBuffer::committedAttributesAdded,
    1660                 :            :                 this, &QgsOfflineEditing::committedAttributesAdded );
    1661                 :          0 :     disconnect( editBuffer, &QgsVectorLayerEditBuffer::committedAttributeValuesChanges,
    1662                 :            :                 this, &QgsOfflineEditing::committedAttributeValuesChanges );
    1663                 :          0 :     disconnect( editBuffer, &QgsVectorLayerEditBuffer::committedGeometriesChanges,
    1664                 :            :                 this, &QgsOfflineEditing::committedGeometriesChanges );
    1665                 :          0 :   }
    1666                 :          0 :   disconnect( vLayer, &QgsVectorLayer::committedFeaturesAdded,
    1667                 :            :               this, &QgsOfflineEditing::committedFeaturesAdded );
    1668                 :          0 :   disconnect( vLayer, &QgsVectorLayer::committedFeaturesRemoved,
    1669                 :            :               this, &QgsOfflineEditing::committedFeaturesRemoved );
    1670                 :          0 : }
    1671                 :            : 
    1672                 :          0 : void QgsOfflineEditing::layerAdded( QgsMapLayer *layer )
    1673                 :            : {
    1674                 :            :   // detect offline layer
    1675                 :          0 :   if ( layer->customProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, false ).toBool() )
    1676                 :            :   {
    1677                 :          0 :     QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( layer );
    1678                 :          0 :     connect( vLayer, &QgsVectorLayer::editingStarted, this, &QgsOfflineEditing::startListenFeatureChanges );
    1679                 :          0 :     connect( vLayer, &QgsVectorLayer::editingStopped, this, &QgsOfflineEditing::stopListenFeatureChanges );
    1680                 :          0 :   }
    1681                 :          0 : }
    1682                 :            : 
    1683                 :            : 

Generated by: LCOV version 1.14