Skip to content
Permalink
Browse files
Add layer validity checks and hints where not to expect nullptr
  • Loading branch information
suricactus authored and nyalldawson committed Jun 13, 2021
1 parent 6938593 commit 8be06d8bbf710fe141a58a69597d2bedf00d3b20
Showing with 137 additions and 77 deletions.
  1. +137 −77 src/core/qgsofflineediting.cpp
@@ -116,8 +116,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
@@ -131,7 +134,7 @@ bool QgsOfflineEditing::convertToOfflineProject( const QString &offlineDataPath,
{
QgsVectorLayer *vl = joinIt->joinLayer();

if ( vl )
if ( vl && vl->isValid() )
joinIt->setPrefix( vl->name() + '_' );
}
++joinIt;
@@ -148,11 +151,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
@@ -174,13 +177,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 );
@@ -234,8 +237,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;
}
}
@@ -270,90 +280,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() ) );
}
}

@@ -544,8 +561,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 );
@@ -776,7 +796,7 @@ QgsVectorLayer *QgsOfflineEditing::copyVectorLayer( QgsVectorLayer *layer, sqlit
}
}

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

// copy features
@@ -930,6 +950,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 );

@@ -967,6 +989,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;
@@ -1013,6 +1038,8 @@ void QgsOfflineEditing::applyFeaturesAdded( QgsVectorLayer *offlineLayer, QgsVec

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 );

@@ -1030,6 +1057,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 );

@@ -1049,6 +1079,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 );

@@ -1066,6 +1098,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
@@ -1112,6 +1146,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;
@@ -1132,6 +1169,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 );

@@ -1154,6 +1194,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();

@@ -1179,6 +1222,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();
@@ -1206,6 +1252,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;
@@ -1632,6 +1681,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() )
{
@@ -1652,6 +1704,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() )
{
@@ -1671,10 +1726,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 8be06d8

Please sign in to comment.