Skip to content
Permalink
Browse files
Add layer validity checks and hints where not to expect nullptr
  • Loading branch information
suricactus committed Jun 10, 2021
1 parent 6aa0cbe commit 3b2ffc27af3d5da2d1dcf7b24cb0a941610c586c
Showing with 137 additions and 77 deletions.
  1. +137 −77 src/core/qgsofflineediting.cpp
@@ -122,8 +122,11 @@ bool QgsOfflineEditing::convertToOfflineProject( const QString &offlineDataPath,
{
QgsMapLayer *layer = QgsProject::instance()->mapLayer( layerId );
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
if ( !vl )
if ( !vl || !vl->isValid() )
{
QgsDebugMsgLevel( QStringLiteral( "Layer %1 is invalid" ).arg( layerId ), 4 );
continue;
}
QgsVectorJoinList joins = vl->vectorJoins();

// Layer names will be appended an _offline suffix
@@ -137,7 +140,7 @@ bool QgsOfflineEditing::convertToOfflineProject( const QString &offlineDataPath,
{
QgsVectorLayer *vl = joinIt->joinLayer();

if ( vl )
if ( vl && vl->isValid() )
joinIt->setPrefix( vl->name() + '_' );
}
++joinIt;
@@ -154,11 +157,11 @@ bool QgsOfflineEditing::convertToOfflineProject( const QString &offlineDataPath,

QgsMapLayer *layer = QgsProject::instance()->mapLayer( layerIds.at( i ) );
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
if ( vl )
if ( vl && vl->isValid() )
{
QString origLayerId = vl->id();
QgsVectorLayer *newLayer = copyVectorLayer( vl, database.get(), dbPath, onlySelected, containerType, layerNameSuffix );
if ( newLayer )
if ( newLayer && newLayer->isValid() )
{
layerIdMapping.insert( origLayerId, newLayer );
//append individual layer setting on snapping settings
@@ -180,13 +183,13 @@ bool QgsOfflineEditing::convertToOfflineProject( const QString &offlineDataPath,
{
QgsVectorLayer *newLayer = layerIdMapping.value( it.key() );

if ( newLayer )
if ( newLayer && newLayer->isValid() )
{
const QList<QgsVectorLayerJoinInfo> joins = it.value();
for ( QgsVectorLayerJoinInfo join : joins )
{
QgsVectorLayer *newJoinedLayer = layerIdMapping.value( join.joinLayerId() );
if ( newJoinedLayer )
if ( newJoinedLayer && newJoinedLayer->isValid() )
{
// If the layer has been offline'd, update join information
join.setJoinLayer( newJoinedLayer );
@@ -240,8 +243,15 @@ void QgsOfflineEditing::synchronize()
for ( QMap<QString, QgsMapLayer *>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
{
QgsMapLayer *layer = layer_it.value();

if ( layer->customProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, false ).toBool() )
{
if ( !layer->isValid() )
{
QgsDebugMsgLevel( QStringLiteral( "Skipping offline layer %1 because it is an invalid layer" ).arg( layer->id() ), 4 );
continue;
}

offlineLayers << layer;
}
}
@@ -276,90 +286,97 @@ void QgsOfflineEditing::synchronize()

QgsVectorLayer *offlineLayer = qobject_cast<QgsVectorLayer *>( layer );

// register this layer with the central layers registry
QgsProject::instance()->addMapLayers( QList<QgsMapLayer *>() << remoteLayer, true );

// copy style
copySymbology( offlineLayer, remoteLayer );
updateRelations( offlineLayer, remoteLayer );
updateMapThemes( offlineLayer, remoteLayer );
updateLayerOrder( offlineLayer, remoteLayer );

//append individual layer setting on snapping settings
snappingConfig.setIndividualLayerSettings( remoteLayer, snappingConfig.individualLayerSettings( offlineLayer ) );
snappingConfig.removeLayers( QList<QgsMapLayer *>() << offlineLayer );

//set QgsLayerTreeNode properties back
QgsLayerTreeLayer *layerTreeLayer = QgsProject::instance()->layerTreeRoot()->findLayer( offlineLayer->id() );
QgsLayerTreeLayer *newLayerTreeLayer = QgsProject::instance()->layerTreeRoot()->findLayer( remoteLayer->id() );
newLayerTreeLayer->setCustomProperty( CUSTOM_SHOW_FEATURE_COUNT, layerTreeLayer->customProperty( CUSTOM_SHOW_FEATURE_COUNT ) );

// apply layer edit log
QString qgisLayerId = layer->id();
QString sql = QStringLiteral( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
int layerId = sqlQueryInt( database.get(), sql, -1 );
if ( layerId != -1 )
if ( offlineLayer->isValid() )
{
remoteLayer->startEditing();

// TODO: only get commitNos of this layer?
int commitNo = getCommitNo( database.get() );
QgsDebugMsgLevel( QStringLiteral( "Found %1 commits" ).arg( commitNo ), 4 );
for ( int i = 0; i < commitNo; i++ )
// register this layer with the central layers registry
QgsProject::instance()->addMapLayers( QList<QgsMapLayer *>() << remoteLayer, true );

// copy style
copySymbology( offlineLayer, remoteLayer );
updateRelations( offlineLayer, remoteLayer );
updateMapThemes( offlineLayer, remoteLayer );
updateLayerOrder( offlineLayer, remoteLayer );

//append individual layer setting on snapping settings
snappingConfig.setIndividualLayerSettings( remoteLayer, snappingConfig.individualLayerSettings( offlineLayer ) );
snappingConfig.removeLayers( QList<QgsMapLayer *>() << offlineLayer );

//set QgsLayerTreeNode properties back
QgsLayerTreeLayer *layerTreeLayer = QgsProject::instance()->layerTreeRoot()->findLayer( offlineLayer->id() );
QgsLayerTreeLayer *newLayerTreeLayer = QgsProject::instance()->layerTreeRoot()->findLayer( remoteLayer->id() );
newLayerTreeLayer->setCustomProperty( CUSTOM_SHOW_FEATURE_COUNT, layerTreeLayer->customProperty( CUSTOM_SHOW_FEATURE_COUNT ) );

// apply layer edit log
QString qgisLayerId = layer->id();
QString sql = QStringLiteral( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
int layerId = sqlQueryInt( database.get(), sql, -1 );
if ( layerId != -1 )
{
QgsDebugMsgLevel( QStringLiteral( "Apply commits chronologically" ), 4 );
// apply commits chronologically
applyAttributesAdded( remoteLayer, database.get(), layerId, i );
applyAttributeValueChanges( offlineLayer, remoteLayer, database.get(), layerId, i );
applyGeometryChanges( remoteLayer, database.get(), layerId, i );
}
remoteLayer->startEditing();

// TODO: only get commitNos of this layer?
int commitNo = getCommitNo( database.get() );
QgsDebugMsgLevel( QStringLiteral( "Found %1 commits" ).arg( commitNo ), 4 );
for ( int i = 0; i < commitNo; i++ )
{
QgsDebugMsgLevel( QStringLiteral( "Apply commits chronologically" ), 4 );
// apply commits chronologically
applyAttributesAdded( remoteLayer, database.get(), layerId, i );
applyAttributeValueChanges( offlineLayer, remoteLayer, database.get(), layerId, i );
applyGeometryChanges( remoteLayer, database.get(), layerId, i );
}

applyFeaturesAdded( offlineLayer, remoteLayer, database.get(), layerId );
applyFeaturesRemoved( remoteLayer, database.get(), layerId );
applyFeaturesAdded( offlineLayer, remoteLayer, database.get(), layerId );
applyFeaturesRemoved( remoteLayer, database.get(), layerId );

if ( remoteLayer->commitChanges() )
{
// update fid lookup
updateFidLookup( remoteLayer, database.get(), layerId );

// clear edit log for this layer
sql = QStringLiteral( "DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( layerId );
sqlExec( database.get(), sql );
sql = QStringLiteral( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
sqlExec( database.get(), sql );
sql = QStringLiteral( "DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
sqlExec( database.get(), sql );
sql = QStringLiteral( "DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
sqlExec( database.get(), sql );
sql = QStringLiteral( "DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
sqlExec( database.get(), sql );
if ( remoteLayer->commitChanges() )
{
// update fid lookup
updateFidLookup( remoteLayer, database.get(), layerId );

// clear edit log for this layer
sql = QStringLiteral( "DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( layerId );
sqlExec( database.get(), sql );
sql = QStringLiteral( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
sqlExec( database.get(), sql );
sql = QStringLiteral( "DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
sqlExec( database.get(), sql );
sql = QStringLiteral( "DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
sqlExec( database.get(), sql );
sql = QStringLiteral( "DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
sqlExec( database.get(), sql );
}
else
{
showWarning( remoteLayer->commitErrors().join( QLatin1Char( '\n' ) ) );
}
}
else
{
showWarning( remoteLayer->commitErrors().join( QLatin1Char( '\n' ) ) );
QgsDebugMsg( QStringLiteral( "Could not find the layer id in the edit logs!" ) );
}
// Invalidate the connection to force a reload if the project is put offline
// again with the same path
offlineLayer->dataProvider()->invalidateConnections( QgsDataSourceUri( offlineLayer->source() ).database() );
// remove offline layer
QgsProject::instance()->removeMapLayers( QStringList() << qgisLayerId );


// disable offline project
QString projectTitle = QgsProject::instance()->title();
projectTitle.remove( QRegExp( " \\(offline\\)$" ) );
QgsProject::instance()->setTitle( projectTitle );
QgsProject::instance()->removeEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH );
remoteLayer->reload(); //update with other changes
}
else
{
QgsDebugMsg( QStringLiteral( "Could not find the layer id in the edit logs!" ) );
QgsDebugMsg( QStringLiteral( "Offline layer %1 is not valid!" ).arg( offlineLayer->id() ) );
}
// Invalidate the connection to force a reload if the project is put offline
// again with the same path
offlineLayer->dataProvider()->invalidateConnections( QgsDataSourceUri( offlineLayer->source() ).database() );
// remove offline layer
QgsProject::instance()->removeMapLayers( QStringList() << qgisLayerId );


// disable offline project
QString projectTitle = QgsProject::instance()->title();
projectTitle.remove( QRegExp( " \\(offline\\)$" ) );
QgsProject::instance()->setTitle( projectTitle );
QgsProject::instance()->removeEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH );
remoteLayer->reload(); //update with other changes
}
else
{
QgsDebugMsg( QStringLiteral( "Remote layer is not valid!" ) );
QgsDebugMsg( QStringLiteral( "Remote layer %1 is not valid!" ).arg( remoteLayer->id() ) );
}
}

@@ -554,8 +571,11 @@ void QgsOfflineEditing::createLoggingTables( sqlite3 *db )

QgsVectorLayer *QgsOfflineEditing::copyVectorLayer( QgsVectorLayer *layer, sqlite3 *db, const QString &offlineDbPath, bool onlySelected, ContainerType containerType, const QString &layerNameSuffix )
{
if ( !layer )
if ( !layer || !layer->isValid() )
{
QgsDebugMsgLevel( QStringLiteral( "Layer %1 is invalid and cannot be coppied" ).arg( layer ? layer->id() : QStringLiteral( "<UNKNOWN>" ) ), 4 );
return nullptr;
}

QString tableName = layer->id();
QgsDebugMsgLevel( QStringLiteral( "Creating offline table %1 ..." ).arg( tableName ), 4 );
@@ -803,7 +823,7 @@ QgsVectorLayer *QgsOfflineEditing::copyVectorLayer( QgsVectorLayer *layer, sqlit
}
}

if ( newLayer->isValid() )
if ( newLayer && newLayer->isValid() )
{

// copy features
@@ -962,6 +982,8 @@ QgsVectorLayer *QgsOfflineEditing::copyVectorLayer( QgsVectorLayer *layer, sqlit

void QgsOfflineEditing::applyAttributesAdded( QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId, int commitNo )
{
Q_ASSERT( remoteLayer );

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 );
QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );

@@ -999,6 +1021,9 @@ void QgsOfflineEditing::applyAttributesAdded( QgsVectorLayer *remoteLayer, sqlit

void QgsOfflineEditing::applyFeaturesAdded( QgsVectorLayer *offlineLayer, QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId )
{
Q_ASSERT( offlineLayer );
Q_ASSERT( remoteLayer );

QString sql = QStringLiteral( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
const QList<int> featureIdInts = sqlQueryInts( db, sql );
QgsFeatureIds newFeatureIds;
@@ -1100,6 +1125,8 @@ QVariantList QgsOfflineEditing::convertStringToList( const QString &string, QVar

void QgsOfflineEditing::applyFeaturesRemoved( QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId )
{
Q_ASSERT( remoteLayer );

QString sql = QStringLiteral( "SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql );

@@ -1117,6 +1144,9 @@ void QgsOfflineEditing::applyFeaturesRemoved( QgsVectorLayer *remoteLayer, sqlit

void QgsOfflineEditing::applyAttributeValueChanges( QgsVectorLayer *offlineLayer, QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId, int commitNo )
{
Q_ASSERT( offlineLayer );
Q_ASSERT( remoteLayer );

QString sql = QStringLiteral( "SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );

@@ -1149,6 +1179,8 @@ void QgsOfflineEditing::applyAttributeValueChanges( QgsVectorLayer *offlineLayer

void QgsOfflineEditing::applyGeometryChanges( QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId, int commitNo )
{
Q_ASSERT( remoteLayer );

QString sql = QStringLiteral( "SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
GeometryChanges values = sqlQueryGeometryChanges( db, sql );

@@ -1166,6 +1198,8 @@ void QgsOfflineEditing::applyGeometryChanges( QgsVectorLayer *remoteLayer, sqlit

void QgsOfflineEditing::updateFidLookup( QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId )
{
Q_ASSERT( remoteLayer );

// update fid lookup for added features

// get remote added fids
@@ -1212,6 +1246,9 @@ void QgsOfflineEditing::updateFidLookup( QgsVectorLayer *remoteLayer, sqlite3 *d

void QgsOfflineEditing::copySymbology( QgsVectorLayer *sourceLayer, QgsVectorLayer *targetLayer )
{
Q_ASSERT( sourceLayer );
Q_ASSERT( targetLayer );

targetLayer->styleManager()->copyStylesFrom( sourceLayer->styleManager() );

QString error;
@@ -1232,6 +1269,9 @@ void QgsOfflineEditing::copySymbology( QgsVectorLayer *sourceLayer, QgsVectorLay

void QgsOfflineEditing::updateRelations( QgsVectorLayer *sourceLayer, QgsVectorLayer *targetLayer )
{
Q_ASSERT( sourceLayer );
Q_ASSERT( targetLayer );

QgsRelationManager *relationManager = QgsProject::instance()->relationManager();
const QList<QgsRelation> referencedRelations = relationManager->referencedRelations( sourceLayer );

@@ -1254,6 +1294,9 @@ void QgsOfflineEditing::updateRelations( QgsVectorLayer *sourceLayer, QgsVectorL

void QgsOfflineEditing::updateMapThemes( QgsVectorLayer *sourceLayer, QgsVectorLayer *targetLayer )
{
Q_ASSERT( sourceLayer );
Q_ASSERT( targetLayer );

QgsMapThemeCollection *mapThemeCollection = QgsProject::instance()->mapThemeCollection();
const QStringList mapThemeNames = mapThemeCollection->mapThemes();

@@ -1279,6 +1322,9 @@ void QgsOfflineEditing::updateMapThemes( QgsVectorLayer *sourceLayer, QgsVectorL

void QgsOfflineEditing::updateLayerOrder( QgsVectorLayer *sourceLayer, QgsVectorLayer *targetLayer )
{
Q_ASSERT( sourceLayer );
Q_ASSERT( targetLayer );

QList<QgsMapLayer *> layerOrder = QgsProject::instance()->layerTreeRoot()->customLayerOrder();

auto iterator = layerOrder.begin();
@@ -1306,6 +1352,9 @@ void QgsOfflineEditing::updateLayerOrder( QgsVectorLayer *sourceLayer, QgsVector
// NOTE: use this to map column indices in case the remote geometry column is not last
QMap<int, int> QgsOfflineEditing::attributeLookup( QgsVectorLayer *offlineLayer, QgsVectorLayer *remoteLayer )
{
Q_ASSERT( offlineLayer );
Q_ASSERT( remoteLayer );

const QgsAttributeList &offlineAttrs = offlineLayer->attributeList();

QMap < int /*offline attr*/, int /*remote attr*/ > attrLookup;
@@ -1732,6 +1781,9 @@ void QgsOfflineEditing::committedGeometriesChanges( const QString &qgisLayerId,
void QgsOfflineEditing::startListenFeatureChanges()
{
QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );

Q_ASSERT( vLayer );

// enable logging, check if editBuffer is not null
if ( vLayer->editBuffer() )
{
@@ -1752,6 +1804,9 @@ void QgsOfflineEditing::startListenFeatureChanges()
void QgsOfflineEditing::stopListenFeatureChanges()
{
QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );

Q_ASSERT( vLayer );

// disable logging, check if editBuffer is not null
if ( vLayer->editBuffer() )
{
@@ -1771,10 +1826,15 @@ void QgsOfflineEditing::stopListenFeatureChanges()

void QgsOfflineEditing::layerAdded( QgsMapLayer *layer )
{
Q_ASSERT( layer );

// detect offline layer
if ( layer->customProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, false ).toBool() )
{
QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( layer );

Q_ASSERT( vLayer );

connect( vLayer, &QgsVectorLayer::editingStarted, this, &QgsOfflineEditing::startListenFeatureChanges );
connect( vLayer, &QgsVectorLayer::editingStopped, this, &QgsOfflineEditing::stopListenFeatureChanges );
}

0 comments on commit 3b2ffc2

Please sign in to comment.